@@ -0,0 +1,37 @@ | |||
[flake8] | |||
ignore = | |||
E121, | |||
E126, | |||
E127, | |||
E128, | |||
E203, | |||
E225, | |||
E226, | |||
E231, | |||
E241, | |||
E251, | |||
E261, | |||
E265, | |||
E302, | |||
E303, | |||
E305, | |||
E402, | |||
E501, | |||
E741, | |||
W291, | |||
W292, | |||
W293, | |||
W391, | |||
W503, | |||
W504, | |||
F403, | |||
B007, | |||
B950, | |||
W191, | |||
E124, # closing bracket, irritating while writing QB code | |||
E131, # continuation line unaligned for hanging indent | |||
E123, # closing bracket does not match indentation of opening bracket's line | |||
E101, # ensured by use of black | |||
max-line-length = 200 | |||
exclude=.github/helper/semgrep_rules |
@@ -0,0 +1,15 @@ | |||
# Since version 2.23 (released in August 2019), git-blame has a feature | |||
# to ignore or bypass certain commits. | |||
# | |||
# This file contains a list of commits that are not likely what you | |||
# are looking for in a blame, such as mass reformatting or renaming. | |||
# You can set this file as a default ignore file for blame by running | |||
# the following command. | |||
# | |||
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs | |||
# sort and cleanup imports | |||
4872c156974291f0c4c88f26033fef0b900ca995 | |||
# old black formatting commit (from erpnext) | |||
76c895a6c659356151433715a1efe9337e348c11 |
@@ -0,0 +1,7 @@ | |||
# This is a comment. | |||
# Each line is a file pattern followed by one or more owners. | |||
# These owners will be the default owners for everything in | |||
# the repo. Unless a later match takes precedence. | |||
* @ruchamahabal |
@@ -0,0 +1,73 @@ | |||
[flake8] | |||
ignore = | |||
B007, | |||
B009, | |||
B010, | |||
B950, | |||
E101, | |||
E111, | |||
E114, | |||
E116, | |||
E117, | |||
E121, | |||
E122, | |||
E123, | |||
E124, | |||
E125, | |||
E126, | |||
E127, | |||
E128, | |||
E131, | |||
E201, | |||
E202, | |||
E203, | |||
E211, | |||
E221, | |||
E222, | |||
E223, | |||
E224, | |||
E225, | |||
E226, | |||
E228, | |||
E231, | |||
E241, | |||
E242, | |||
E251, | |||
E261, | |||
E262, | |||
E265, | |||
E266, | |||
E271, | |||
E272, | |||
E273, | |||
E274, | |||
E301, | |||
E302, | |||
E303, | |||
E305, | |||
E306, | |||
E402, | |||
E501, | |||
E502, | |||
E701, | |||
E702, | |||
E703, | |||
E741, | |||
F403, | |||
W191, | |||
W291, | |||
W292, | |||
W293, | |||
W391, | |||
W503, | |||
W504, | |||
E711, | |||
E129, | |||
F841, | |||
E713, | |||
E712, | |||
B023 | |||
max-line-length = 200 | |||
exclude=.github/helper/semgrep_rules,test_*.py |
@@ -0,0 +1,54 @@ | |||
import sys | |||
import requests | |||
from urllib.parse import urlparse | |||
docs_repos = [ | |||
"frappe_docs", | |||
"erpnext_documentation", | |||
"erpnext_com", | |||
"frappe_io", | |||
] | |||
def uri_validator(x): | |||
result = urlparse(x) | |||
return all([result.scheme, result.netloc, result.path]) | |||
def docs_link_exists(body): | |||
for line in body.splitlines(): | |||
for word in line.split(): | |||
if word.startswith('http') and uri_validator(word): | |||
parsed_url = urlparse(word) | |||
if parsed_url.netloc == "github.com": | |||
parts = parsed_url.path.split('/') | |||
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos: | |||
return True | |||
elif parsed_url.netloc == "docs.erpnext.com": | |||
return True | |||
if __name__ == "__main__": | |||
pr = sys.argv[1] | |||
response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr)) | |||
if response.ok: | |||
payload = response.json() | |||
title = (payload.get("title") or "").lower().strip() | |||
head_sha = (payload.get("head") or {}).get("sha") | |||
body = (payload.get("body") or "").lower() | |||
if (title.startswith("feat") | |||
and head_sha | |||
and "no-docs" not in body | |||
and "backport" not in body | |||
): | |||
if docs_link_exists(body): | |||
print("Documentation Link Found. You're Awesome! 🎉") | |||
else: | |||
print("Documentation Link Not Found! ⚠️") | |||
sys.exit(1) | |||
else: | |||
print("Skipping documentation checks... 🏃") |
@@ -0,0 +1,52 @@ | |||
#!/bin/bash | |||
set -e | |||
cd ~ || exit | |||
sudo apt-get -y install redis-server libcups2-dev -qq | |||
pip install frappe-bench | |||
git clone https://github.com/frappe/frappe --branch develop --depth 1 | |||
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench | |||
mkdir ~/frappe-bench/sites/test_site | |||
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/ | |||
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'" | |||
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" | |||
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" | |||
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe" | |||
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" | |||
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'" | |||
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES" | |||
install_whktml() { | |||
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz | |||
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp | |||
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf | |||
sudo chmod o+x /usr/local/bin/wkhtmltopdf | |||
} | |||
install_whktml & | |||
cd ~/frappe-bench || exit | |||
sed -i 's/watch:/# watch:/g' Procfile | |||
sed -i 's/schedule:/# schedule:/g' Procfile | |||
sed -i 's/socketio:/# socketio:/g' Procfile | |||
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile | |||
bench get-app payments | |||
bench get-app https://github.com/frappe/erpnext --branch develop | |||
bench setup requirements --dev | |||
bench start &> bench_run_logs.txt & | |||
CI=Yes bench build --app frappe & | |||
bench --site test_site reinstall --yes | |||
bench get-app hrms "${GITHUB_WORKSPACE}" | |||
bench --site test_site install-app hrms | |||
bench setup requirements --dev |
@@ -0,0 +1,16 @@ | |||
{ | |||
"db_host": "127.0.0.1", | |||
"db_port": 3306, | |||
"db_name": "test_frappe", | |||
"db_password": "test_frappe", | |||
"auto_email_id": "test@example.com", | |||
"mail_server": "smtp.example.com", | |||
"mail_login": "test@example.com", | |||
"mail_password": "test", | |||
"admin_password": "admin", | |||
"root_login": "root", | |||
"root_password": "travis", | |||
"host_name": "http://test_site:8000", | |||
"install_apps": ["erpnext"], | |||
"throttle_user_limit": 100 | |||
} |
@@ -0,0 +1,60 @@ | |||
import re | |||
import sys | |||
errors_encounter = 0 | |||
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)") | |||
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]") | |||
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}") | |||
f_string_pattern = re.compile(r"_\(f[\"']") | |||
starts_with_f_pattern = re.compile(r"_\(f") | |||
# skip first argument | |||
files = sys.argv[1:] | |||
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))] | |||
for _file in files_to_scan: | |||
with open(_file, 'r') as f: | |||
print(f'Checking: {_file}') | |||
file_lines = f.readlines() | |||
for line_number, line in enumerate(file_lines, 1): | |||
if 'frappe-lint: disable-translate' in line: | |||
continue | |||
start_matches = start_pattern.search(line) | |||
if start_matches: | |||
starts_with_f = starts_with_f_pattern.search(line) | |||
if starts_with_f: | |||
has_f_string = f_string_pattern.search(line) | |||
if has_f_string: | |||
errors_encounter += 1 | |||
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}') | |||
continue | |||
else: | |||
continue | |||
match = pattern.search(line) | |||
error_found = False | |||
if not match and line.endswith((',\n', '[\n')): | |||
# concat remaining text to validate multiline pattern | |||
line = "".join(file_lines[line_number - 1:]) | |||
line = line[start_matches.start() + 1:] | |||
match = pattern.match(line) | |||
if not match: | |||
error_found = True | |||
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}') | |||
if not error_found and not words_pattern.search(line): | |||
error_found = True | |||
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}') | |||
if error_found: | |||
errors_encounter += 1 | |||
if errors_encounter > 0: | |||
print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.') | |||
sys.exit(1) | |||
else: | |||
print('\nGood To Go!') |
@@ -0,0 +1,102 @@ | |||
name: CI | |||
on: | |||
push: | |||
branches: | |||
- develop | |||
pull_request: | |||
branches: | |||
- develop | |||
schedule: | |||
# Run everday at midnight UTC / 5:30 IST | |||
- cron: "0 0 * * *" | |||
concurrency: | |||
group: develop-${{ github.event.number }} | |||
cancel-in-progress: true | |||
jobs: | |||
tests: | |||
runs-on: ubuntu-latest | |||
timeout-minutes: 60 | |||
strategy: | |||
fail-fast: false | |||
name: Server | |||
services: | |||
mysql: | |||
image: mariadb:10.3 | |||
env: | |||
MYSQL_ALLOW_EMPTY_PASSWORD: YES | |||
ports: | |||
- 3306:3306 | |||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 | |||
steps: | |||
- name: Clone | |||
uses: actions/checkout@v2 | |||
- name: Setup Python | |||
uses: actions/setup-python@v2 | |||
with: | |||
python-version: '3.10' | |||
- name: Setup Node | |||
uses: actions/setup-node@v2 | |||
with: | |||
node-version: 14 | |||
check-latest: true | |||
- name: Add to Hosts | |||
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts | |||
- name: Cache pip | |||
uses: actions/cache@v2 | |||
with: | |||
path: ~/.cache/pip | |||
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt') }} | |||
restore-keys: | | |||
${{ runner.os }}-pip- | |||
${{ runner.os }}- | |||
- name: Cache node modules | |||
uses: actions/cache@v2 | |||
env: | |||
cache-name: cache-node-modules | |||
with: | |||
path: ~/.npm | |||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} | |||
restore-keys: | | |||
${{ runner.os }}-build-${{ env.cache-name }}- | |||
${{ runner.os }}-build- | |||
${{ runner.os }}- | |||
- name: Get yarn cache directory path | |||
id: yarn-cache-dir-path | |||
run: echo "::set-output name=dir::$(yarn cache dir)" | |||
- uses: actions/cache@v2 | |||
id: yarn-cache | |||
with: | |||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | |||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} | |||
restore-keys: | | |||
${{ runner.os }}-yarn- | |||
- name: Install | |||
run: | | |||
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh | |||
- name: Run Tests | |||
run: cd ~/frappe-bench/ && bench --site test_site run-tests --app hrms --coverage | |||
env: | |||
TYPE: server | |||
- name: Upload coverage data | |||
uses: codecov/codecov-action@v2 | |||
with: | |||
fail_ci_if_error: true | |||
files: /home/runner/frappe-bench/sites/coverage.xml | |||
verbose: true |
@@ -0,0 +1,25 @@ | |||
name: 'Documentation Required' | |||
on: | |||
pull_request: | |||
types: [ opened, synchronize, reopened, edited ] | |||
jobs: | |||
build: | |||
runs-on: ubuntu-latest | |||
timeout-minutes: 10 | |||
steps: | |||
- name: 'Setup Environment' | |||
uses: actions/setup-python@v2 | |||
with: | |||
python-version: 3.8 | |||
- name: 'Clone repo' | |||
uses: actions/checkout@v2 | |||
- name: Validate Docs | |||
env: | |||
PR_NUMBER: ${{ github.event.number }} | |||
run: | | |||
pip install requests --quiet | |||
python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER |
@@ -0,0 +1,29 @@ | |||
name: Linters | |||
on: | |||
pull_request: { } | |||
jobs: | |||
linters: | |||
name: linters | |||
runs-on: ubuntu-latest | |||
steps: | |||
- uses: actions/checkout@v2 | |||
- name: Set up Python 3.10 | |||
uses: actions/setup-python@v2 | |||
with: | |||
python-version: '3.10' | |||
- name: Install and Run Pre-commit | |||
uses: pre-commit/action@v2.0.3 | |||
- name: Download Semgrep rules | |||
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules | |||
- name: Download semgrep | |||
run: pip install semgrep==0.97.0 | |||
- name: Run Semgrep rules | |||
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness |
@@ -0,0 +1,9 @@ | |||
.DS_Store | |||
*.pyc | |||
*.egg-info | |||
*.swp | |||
tags | |||
hrms/docs/current | |||
node_modules/ | |||
dist/ | |||
__pycache__/ |
@@ -0,0 +1,44 @@ | |||
exclude: 'node_modules|.git' | |||
default_stages: [commit] | |||
fail_fast: false | |||
repos: | |||
- repo: https://github.com/pre-commit/pre-commit-hooks | |||
rev: v4.0.1 | |||
hooks: | |||
- id: trailing-whitespace | |||
files: "hrms.*" | |||
exclude: ".*json$|.*txt$|.*csv|.*md" | |||
- id: check-yaml | |||
- id: no-commit-to-branch | |||
args: ['--branch', 'develop'] | |||
- id: check-merge-conflict | |||
- id: check-ast | |||
- repo: https://gitlab.com/pycqa/flake8 | |||
rev: 3.9.2 | |||
hooks: | |||
- id: flake8 | |||
additional_dependencies: [ | |||
'flake8-bugbear', | |||
] | |||
args: ['--config', '.github/helper/.flake8_strict'] | |||
exclude: ".*setup.py$" | |||
- repo: https://github.com/adityahase/black | |||
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119 | |||
hooks: | |||
- id: black | |||
additional_dependencies: ['click==8.0.4'] | |||
- repo: https://github.com/timothycrosley/isort | |||
rev: 5.9.1 | |||
hooks: | |||
- id: isort | |||
exclude: ".*setup.py$" | |||
ci: | |||
autoupdate_schedule: weekly | |||
skip: [] | |||
submodules: false |
@@ -0,0 +1 @@ | |||
hrms/patches/post_install/ |
@@ -0,0 +1,18 @@ | |||
include MANIFEST.in | |||
include requirements.txt | |||
include *.json | |||
include *.md | |||
include *.py | |||
include *.txt | |||
recursive-include hrms *.css | |||
recursive-include hrms *.csv | |||
recursive-include hrms *.html | |||
recursive-include hrms *.ico | |||
recursive-include hrms *.js | |||
recursive-include hrms *.json | |||
recursive-include hrms *.md | |||
recursive-include hrms *.png | |||
recursive-include hrms *.py | |||
recursive-include hrms *.svg | |||
recursive-include hrms *.txt | |||
recursive-exclude hrms *.pyc |
@@ -0,0 +1,52 @@ | |||
# Frappe HR | |||
Open Source, modern, and easy-to-use HR and Payroll Software for all organizations. | |||
[](https://github.com/frappe/hrms/actions/workflows/ci.yml) | |||
[](https://codecov.io/gh/frappe/hrms) | |||
## Introduction | |||
Frappe HR has everything you need to drive excellence within the company. It's a complete HRMS solution with over 13 different modules right from Employee Management, Onboarding, Leaves, to Payroll, Taxation, and more! | |||
 | |||
## Key Features | |||
- Employee Management | |||
- Employee Lifecycle | |||
- Leave and Attendance | |||
- Shift Management | |||
- Expense Claims and Advances | |||
- Hiring | |||
- Performance Management | |||
- Fleet Management | |||
- Training | |||
- Payroll | |||
- Taxation | |||
- Compensation | |||
- Analytics | |||
## Installation | |||
1. [Install bench](https://github.com/frappe/bench). | |||
2. [Install ERPNext](https://github.com/frappe/bench#installation). | |||
3. Once ERPNext is installed, add the hrms app to your bench by running | |||
```sh | |||
$ bench get-app hrms | |||
``` | |||
4. After that, you can install the hrms app on the required site by running | |||
```sh | |||
$ bench --site sitename install-app hrms | |||
``` | |||
## Documentation | |||
Complete documentation for Frappe HR is available at https://docs.erpnext.com/docs/user/manual/en/human-resources | |||
## License | |||
GNU GPL V3. (See [license.txt](license.txt) for more information). | |||
The HR code is licensed as GNU General Public License (v3) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors. |
@@ -0,0 +1,24 @@ | |||
codecov: | |||
require_ci_to_pass: yes | |||
coverage: | |||
status: | |||
project: | |||
default: | |||
target: auto | |||
threshold: 0.5% | |||
patch: | |||
default: | |||
target: 85% | |||
threshold: 0% | |||
base: auto | |||
branches: | |||
- develop | |||
if_ci_failed: ignore | |||
only_pulls: true | |||
comment: | |||
layout: "diff, files" | |||
require_changes: true | |||
@@ -0,0 +1 @@ | |||
__version__ = "0.0.1" |
@@ -0,0 +1,5 @@ | |||
from frappe import _ | |||
def get_data(): | |||
return [{"module_name": "HRMS", "type": "module", "label": _("HRMS")}] |
@@ -0,0 +1,11 @@ | |||
""" | |||
Configuration for docs | |||
""" | |||
# source_link = "https://github.com/[org_name]/hrms" | |||
# headline = "App that does everything" | |||
# sub_heading = "Yes, you got that right the first time, everything" | |||
def get_context(context): | |||
context.brand_html = "HRMS" |
@@ -0,0 +1,198 @@ | |||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | |||
# License: GNU General Public License v3. See license.txt | |||
import frappe | |||
from frappe import _ | |||
from frappe.desk.form import assign_to | |||
from frappe.model.document import Document | |||
from frappe.utils import add_days, flt, unique | |||
from erpnext.setup.doctype.employee.employee import get_holiday_list_for_employee | |||
from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday | |||
class EmployeeBoardingController(Document): | |||
""" | |||
Create the project and the task for the boarding process | |||
Assign to the concerned person and roles as per the onboarding/separation template | |||
""" | |||
def validate(self): | |||
# remove the task if linked before submitting the form | |||
if self.amended_from: | |||
for activity in self.activities: | |||
activity.task = "" | |||
def on_submit(self): | |||
# create the project for the given employee onboarding | |||
project_name = _(self.doctype) + " : " | |||
if self.doctype == "Employee Onboarding": | |||
project_name += self.job_applicant | |||
else: | |||
project_name += self.employee | |||
project = frappe.get_doc( | |||
{ | |||
"doctype": "Project", | |||
"project_name": project_name, | |||
"expected_start_date": self.date_of_joining | |||
if self.doctype == "Employee Onboarding" | |||
else self.resignation_letter_date, | |||
"department": self.department, | |||
"company": self.company, | |||
} | |||
).insert(ignore_permissions=True, ignore_mandatory=True) | |||
self.db_set("project", project.name) | |||
self.db_set("boarding_status", "Pending") | |||
self.reload() | |||
self.create_task_and_notify_user() | |||
def create_task_and_notify_user(self): | |||
# create the task for the given project and assign to the concerned person | |||
holiday_list = self.get_holiday_list() | |||
for activity in self.activities: | |||
if activity.task: | |||
continue | |||
dates = self.get_task_dates(activity, holiday_list) | |||
task = frappe.get_doc( | |||
{ | |||
"doctype": "Task", | |||
"project": self.project, | |||
"subject": activity.activity_name + " : " + self.employee_name, | |||
"description": activity.description, | |||
"department": self.department, | |||
"company": self.company, | |||
"task_weight": activity.task_weight, | |||
"exp_start_date": dates[0], | |||
"exp_end_date": dates[1], | |||
} | |||
).insert(ignore_permissions=True) | |||
activity.db_set("task", task.name) | |||
users = [activity.user] if activity.user else [] | |||
if activity.role: | |||
user_list = frappe.db.sql_list( | |||
""" | |||
SELECT | |||
DISTINCT(has_role.parent) | |||
FROM | |||
`tabHas Role` has_role | |||
LEFT JOIN `tabUser` user | |||
ON has_role.parent = user.name | |||
WHERE | |||
has_role.parenttype = 'User' | |||
AND user.enabled = 1 | |||
AND has_role.role = %s | |||
""", | |||
activity.role, | |||
) | |||
users = unique(users + user_list) | |||
if "Administrator" in users: | |||
users.remove("Administrator") | |||
# assign the task the users | |||
if users: | |||
self.assign_task_to_users(task, users) | |||
def get_holiday_list(self): | |||
if self.doctype == "Employee Separation": | |||
return get_holiday_list_for_employee(self.employee) | |||
else: | |||
if self.employee: | |||
return get_holiday_list_for_employee(self.employee) | |||
else: | |||
if not self.holiday_list: | |||
frappe.throw(_("Please set the Holiday List."), frappe.MandatoryError) | |||
else: | |||
return self.holiday_list | |||
def get_task_dates(self, activity, holiday_list): | |||
start_date = end_date = None | |||
if activity.begin_on is not None: | |||
start_date = add_days(self.boarding_begins_on, activity.begin_on) | |||
start_date = self.update_if_holiday(start_date, holiday_list) | |||
if activity.duration is not None: | |||
end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration) | |||
end_date = self.update_if_holiday(end_date, holiday_list) | |||
return [start_date, end_date] | |||
def update_if_holiday(self, date, holiday_list): | |||
while is_holiday(holiday_list, date): | |||
date = add_days(date, 1) | |||
return date | |||
def assign_task_to_users(self, task, users): | |||
for user in users: | |||
args = { | |||
"assign_to": [user], | |||
"doctype": task.doctype, | |||
"name": task.name, | |||
"description": task.description or task.subject, | |||
"notify": self.notify_users_by_email, | |||
} | |||
assign_to.add(args) | |||
def on_cancel(self): | |||
# delete task project | |||
project = self.project | |||
for task in frappe.get_all("Task", filters={"project": project}): | |||
frappe.delete_doc("Task", task.name, force=1) | |||
frappe.delete_doc("Project", project, force=1) | |||
self.db_set("project", "") | |||
for activity in self.activities: | |||
activity.db_set("task", "") | |||
frappe.msgprint( | |||
_("Linked Project {} and Tasks deleted.").format(project), alert=True, indicator="blue" | |||
) | |||
@frappe.whitelist() | |||
def get_onboarding_details(parent, parenttype): | |||
return frappe.get_all( | |||
"Employee Boarding Activity", | |||
fields=[ | |||
"activity_name", | |||
"role", | |||
"user", | |||
"required_for_employee_creation", | |||
"description", | |||
"task_weight", | |||
"begin_on", | |||
"duration", | |||
], | |||
filters={"parent": parent, "parenttype": parenttype}, | |||
order_by="idx", | |||
) | |||
def update_employee_boarding_status(project, event=None): | |||
employee_onboarding = frappe.db.exists("Employee Onboarding", {"project": project.name}) | |||
employee_separation = frappe.db.exists("Employee Separation", {"project": project.name}) | |||
if not (employee_onboarding or employee_separation): | |||
return | |||
status = "Pending" | |||
if flt(project.percent_complete) > 0.0 and flt(project.percent_complete) < 100.0: | |||
status = "In Process" | |||
elif flt(project.percent_complete) == 100.0: | |||
status = "Completed" | |||
if employee_onboarding: | |||
frappe.db.set_value("Employee Onboarding", employee_onboarding, "boarding_status", status) | |||
elif employee_separation: | |||
frappe.db.set_value("Employee Separation", employee_separation, "boarding_status", status) | |||
def update_task(task, event=None): | |||
if task.project and not task.flags.from_project: | |||
update_employee_boarding_status(frappe.get_cached_doc("Project", task.project)) |
@@ -0,0 +1,275 @@ | |||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | |||
# License: GNU General Public License v3. See license.txt | |||
import frappe | |||
from frappe import _ | |||
from frappe.utils import add_days, add_months, comma_sep, getdate, today | |||
from erpnext.setup.doctype.employee.employee import get_all_employee_emails, get_employee_email | |||
from hrms.hr.utils import get_holidays_for_employee | |||
# ----------------- | |||
# HOLIDAY REMINDERS | |||
# ----------------- | |||
def send_reminders_in_advance_weekly(): | |||
to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders")) | |||
frequency = frappe.db.get_single_value("HR Settings", "frequency") | |||
if not (to_send_in_advance and frequency == "Weekly"): | |||
return | |||
send_advance_holiday_reminders("Weekly") | |||
def send_reminders_in_advance_monthly(): | |||
to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders")) | |||
frequency = frappe.db.get_single_value("HR Settings", "frequency") | |||
if not (to_send_in_advance and frequency == "Monthly"): | |||
return | |||
send_advance_holiday_reminders("Monthly") | |||
def send_advance_holiday_reminders(frequency): | |||
"""Send Holiday Reminders in Advance to Employees | |||
`frequency` (str): 'Weekly' or 'Monthly' | |||
""" | |||
if frequency == "Weekly": | |||
start_date = getdate() | |||
end_date = add_days(getdate(), 7) | |||
elif frequency == "Monthly": | |||
# Sent on 1st of every month | |||
start_date = getdate() | |||
end_date = add_months(getdate(), 1) | |||
else: | |||
return | |||
employees = frappe.db.get_all("Employee", filters={"status": "Active"}, pluck="name") | |||
for employee in employees: | |||
holidays = get_holidays_for_employee( | |||
employee, start_date, end_date, only_non_weekly=True, raise_exception=False | |||
) | |||
send_holidays_reminder_in_advance(employee, holidays) | |||
def send_holidays_reminder_in_advance(employee, holidays): | |||
if not holidays: | |||
return | |||
employee_doc = frappe.get_doc("Employee", employee) | |||
employee_email = get_employee_email(employee_doc) | |||
frequency = frappe.db.get_single_value("HR Settings", "frequency") | |||
email_header = _("Holidays this Month.") if frequency == "Monthly" else _("Holidays this Week.") | |||
frappe.sendmail( | |||
recipients=[employee_email], | |||
subject=_("Upcoming Holidays Reminder"), | |||
template="holiday_reminder", | |||
args=dict( | |||
reminder_text=_("Hey {}! This email is to remind you about the upcoming holidays.").format( | |||
employee_doc.get("first_name") | |||
), | |||
message=_("Below is the list of upcoming holidays for you:"), | |||
advance_holiday_reminder=True, | |||
holidays=holidays, | |||
frequency=frequency[:-2], | |||
), | |||
header=email_header, | |||
) | |||
# ------------------ | |||
# BIRTHDAY REMINDERS | |||
# ------------------ | |||
def send_birthday_reminders(): | |||
"""Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set.""" | |||
to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders")) | |||
if not to_send: | |||
return | |||
employees_born_today = get_employees_who_are_born_today() | |||
for company, birthday_persons in employees_born_today.items(): | |||
employee_emails = get_all_employee_emails(company) | |||
birthday_person_emails = [get_employee_email(doc) for doc in birthday_persons] | |||
recipients = list(set(employee_emails) - set(birthday_person_emails)) | |||
reminder_text, message = get_birthday_reminder_text_and_message(birthday_persons) | |||
send_birthday_reminder(recipients, reminder_text, birthday_persons, message) | |||
if len(birthday_persons) > 1: | |||
# special email for people sharing birthdays | |||
for person in birthday_persons: | |||
person_email = person["user_id"] or person["personal_email"] or person["company_email"] | |||
others = [d for d in birthday_persons if d != person] | |||
reminder_text, message = get_birthday_reminder_text_and_message(others) | |||
send_birthday_reminder(person_email, reminder_text, others, message) | |||
def get_birthday_reminder_text_and_message(birthday_persons): | |||
if len(birthday_persons) == 1: | |||
birthday_person_text = birthday_persons[0]["name"] | |||
else: | |||
# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim | |||
person_names = [d["name"] for d in birthday_persons] | |||
birthday_person_text = comma_sep(person_names, frappe._("{0} & {1}"), False) | |||
reminder_text = _("Today is {0}'s birthday 🎉").format(birthday_person_text) | |||
message = _("A friendly reminder of an important date for our team.") | |||
message += "<br>" | |||
message += _("Everyone, let’s congratulate {0} on their birthday.").format(birthday_person_text) | |||
return reminder_text, message | |||
def send_birthday_reminder(recipients, reminder_text, birthday_persons, message): | |||
frappe.sendmail( | |||
recipients=recipients, | |||
subject=_("Birthday Reminder"), | |||
template="birthday_reminder", | |||
args=dict( | |||
reminder_text=reminder_text, | |||
birthday_persons=birthday_persons, | |||
message=message, | |||
), | |||
header=_("Birthday Reminder 🎂"), | |||
) | |||
def get_employees_who_are_born_today(): | |||
"""Get all employee born today & group them based on their company""" | |||
return get_employees_having_an_event_today("birthday") | |||
def get_employees_having_an_event_today(event_type): | |||
"""Get all employee who have `event_type` today | |||
& group them based on their company. `event_type` | |||
can be `birthday` or `work_anniversary`""" | |||
from collections import defaultdict | |||
# Set column based on event type | |||
if event_type == "birthday": | |||
condition_column = "date_of_birth" | |||
elif event_type == "work_anniversary": | |||
condition_column = "date_of_joining" | |||
else: | |||
return | |||
employees_born_today = frappe.db.multisql( | |||
{ | |||
"mariadb": f""" | |||
SELECT `personal_email`, `company`, `company_email`, `user_id`, `employee_name` AS 'name', `image`, `date_of_joining` | |||
FROM `tabEmployee` | |||
WHERE | |||
DAY({condition_column}) = DAY(%(today)s) | |||
AND | |||
MONTH({condition_column}) = MONTH(%(today)s) | |||
AND | |||
YEAR({condition_column}) < YEAR(%(today)s) | |||
AND | |||
`status` = 'Active' | |||
""", | |||
"postgres": f""" | |||
SELECT "personal_email", "company", "company_email", "user_id", "employee_name" AS 'name', "image" | |||
FROM "tabEmployee" | |||
WHERE | |||
DATE_PART('day', {condition_column}) = date_part('day', %(today)s) | |||
AND | |||
DATE_PART('month', {condition_column}) = date_part('month', %(today)s) | |||
AND | |||
DATE_PART('year', {condition_column}) < date_part('year', %(today)s) | |||
AND | |||
"status" = 'Active' | |||
""", | |||
}, | |||
dict(today=today(), condition_column=condition_column), | |||
as_dict=1, | |||
) | |||
grouped_employees = defaultdict(lambda: []) | |||
for employee_doc in employees_born_today: | |||
grouped_employees[employee_doc.get("company")].append(employee_doc) | |||
return grouped_employees | |||
# -------------------------- | |||
# WORK ANNIVERSARY REMINDERS | |||
# -------------------------- | |||
def send_work_anniversary_reminders(): | |||
"""Send Employee Work Anniversary Reminders if 'Send Work Anniversary Reminders' is checked""" | |||
to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders")) | |||
if not to_send: | |||
return | |||
employees_joined_today = get_employees_having_an_event_today("work_anniversary") | |||
for company, anniversary_persons in employees_joined_today.items(): | |||
employee_emails = get_all_employee_emails(company) | |||
anniversary_person_emails = [get_employee_email(doc) for doc in anniversary_persons] | |||
recipients = list(set(employee_emails) - set(anniversary_person_emails)) | |||
reminder_text, message = get_work_anniversary_reminder_text_and_message(anniversary_persons) | |||
send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message) | |||
if len(anniversary_persons) > 1: | |||
# email for people sharing work anniversaries | |||
for person in anniversary_persons: | |||
person_email = person["user_id"] or person["personal_email"] or person["company_email"] | |||
others = [d for d in anniversary_persons if d != person] | |||
reminder_text, message = get_work_anniversary_reminder_text_and_message(others) | |||
send_work_anniversary_reminder(person_email, reminder_text, others, message) | |||
def get_work_anniversary_reminder_text_and_message(anniversary_persons): | |||
if len(anniversary_persons) == 1: | |||
anniversary_person = anniversary_persons[0]["name"] | |||
persons_name = anniversary_person | |||
# Number of years completed at the company | |||
completed_years = getdate().year - anniversary_persons[0]["date_of_joining"].year | |||
anniversary_person += f" completed {get_pluralized_years(completed_years)}" | |||
else: | |||
person_names_with_years = [] | |||
names = [] | |||
for person in anniversary_persons: | |||
person_text = person["name"] | |||
names.append(person_text) | |||
# Number of years completed at the company | |||
completed_years = getdate().year - person["date_of_joining"].year | |||
person_text += f" completed {get_pluralized_years(completed_years)}" | |||
person_names_with_years.append(person_text) | |||
# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim | |||
anniversary_person = comma_sep(person_names_with_years, frappe._("{0} & {1}"), False) | |||
persons_name = comma_sep(names, frappe._("{0} & {1}"), False) | |||
reminder_text = _("Today {0} at our Company! 🎉").format(anniversary_person) | |||
message = _("A friendly reminder of an important date for our team.") | |||
message += "<br>" | |||
message += _("Everyone, let’s congratulate {0} on their work anniversary!").format(persons_name) | |||
return reminder_text, message | |||
def get_pluralized_years(years): | |||
if years == 1: | |||
return "1 year" | |||
return f"{years} years" | |||
def send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message): | |||
frappe.sendmail( | |||
recipients=recipients, | |||
subject=_("Work Anniversary Reminder"), | |||
template="anniversary_reminder", | |||
args=dict( | |||
reminder_text=reminder_text, | |||
anniversary_persons=anniversary_persons, | |||
message=message, | |||
), | |||
header=_("Work Anniversary Reminder"), | |||
) |
@@ -0,0 +1,269 @@ | |||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | |||
# License: GNU General Public License v3. See license.txt | |||
import unittest | |||
from datetime import timedelta | |||
import frappe | |||
from frappe.utils import add_months, getdate | |||
from erpnext.setup.doctype.employee.test_employee import make_employee | |||
from hrms.controllers.employee_reminders import send_holidays_reminder_in_advance | |||
from hrms.hr.doctype.hr_settings.hr_settings import set_proceed_with_frequency_change | |||
from hrms.hr.utils import get_holidays_for_employee | |||
class TestEmployeeReminders(unittest.TestCase): | |||
@classmethod | |||
def setUpClass(cls): | |||
from erpnext.setup.doctype.holiday_list.test_holiday_list import make_holiday_list | |||
# Create a test holiday list | |||
test_holiday_dates = cls.get_test_holiday_dates() | |||
test_holiday_list = make_holiday_list( | |||
"TestHolidayRemindersList", | |||
holiday_dates=[ | |||
{"holiday_date": test_holiday_dates[0], "description": "test holiday1"}, | |||
{"holiday_date": test_holiday_dates[1], "description": "test holiday2"}, | |||
{"holiday_date": test_holiday_dates[2], "description": "test holiday3", "weekly_off": 1}, | |||
{"holiday_date": test_holiday_dates[3], "description": "test holiday4"}, | |||
{"holiday_date": test_holiday_dates[4], "description": "test holiday5"}, | |||
{"holiday_date": test_holiday_dates[5], "description": "test holiday6"}, | |||
], | |||
from_date=getdate() - timedelta(days=10), | |||
to_date=getdate() + timedelta(weeks=5), | |||
) | |||
# Create a test employee | |||
test_employee = frappe.get_doc( | |||
"Employee", make_employee("test@gopher.io", company="_Test Company") | |||
) | |||
# Attach the holiday list to employee | |||
test_employee.holiday_list = test_holiday_list.name | |||
test_employee.save() | |||
# Attach to class | |||
cls.test_employee = test_employee | |||
cls.test_holiday_dates = test_holiday_dates | |||
# Employee without holidays in this month/week | |||
test_employee_2 = make_employee("test@empwithoutholiday.io", company="_Test Company") | |||
test_employee_2 = frappe.get_doc("Employee", test_employee_2) | |||
test_holiday_list = make_holiday_list( | |||
"TestHolidayRemindersList2", | |||
holiday_dates=[ | |||
{"holiday_date": add_months(getdate(), 1), "description": "test holiday1"}, | |||
], | |||
from_date=add_months(getdate(), -2), | |||
to_date=add_months(getdate(), 2), | |||
) | |||
test_employee_2.holiday_list = test_holiday_list.name | |||
test_employee_2.save() | |||
cls.test_employee_2 = test_employee_2 | |||
cls.holiday_list_2 = test_holiday_list | |||
@classmethod | |||
def get_test_holiday_dates(cls): | |||
today_date = getdate() | |||
return [ | |||
today_date, | |||
today_date - timedelta(days=4), | |||
today_date - timedelta(days=3), | |||
today_date + timedelta(days=1), | |||
today_date + timedelta(days=3), | |||
today_date + timedelta(weeks=3), | |||
] | |||
def setUp(self): | |||
# Clear Email Queue | |||
frappe.db.sql("delete from `tabEmail Queue`") | |||
frappe.db.sql("delete from `tabEmail Queue Recipient`") | |||
def test_is_holiday(self): | |||
from erpnext.setup.doctype.employee.employee import is_holiday | |||
self.assertTrue(is_holiday(self.test_employee.name)) | |||
self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[1])) | |||
self.assertFalse(is_holiday(self.test_employee.name, date=getdate() - timedelta(days=1))) | |||
# Test weekly_off holidays | |||
self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[2])) | |||
self.assertFalse( | |||
is_holiday(self.test_employee.name, date=self.test_holiday_dates[2], only_non_weekly=True) | |||
) | |||
# Test with descriptions | |||
has_holiday, descriptions = is_holiday(self.test_employee.name, with_description=True) | |||
self.assertTrue(has_holiday) | |||
self.assertTrue("test holiday1" in descriptions) | |||
def test_birthday_reminders(self): | |||
employee = frappe.get_doc( | |||
"Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0] | |||
) | |||
employee.date_of_birth = "1992" + frappe.utils.nowdate()[4:] | |||
employee.company_email = "test@example.com" | |||
employee.company = "_Test Company" | |||
employee.save() | |||
from hrms.controllers.employee_reminders import ( | |||
get_employees_who_are_born_today, | |||
send_birthday_reminders, | |||
) | |||
employees_born_today = get_employees_who_are_born_today() | |||
self.assertTrue(employees_born_today.get("_Test Company")) | |||
hr_settings = frappe.get_doc("HR Settings", "HR Settings") | |||
hr_settings.send_birthday_reminders = 1 | |||
hr_settings.save() | |||
send_birthday_reminders() | |||
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) | |||
self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message) | |||
def test_work_anniversary_reminders(self): | |||
from hrms.controllers.employee_reminders import ( | |||
get_employees_having_an_event_today, | |||
send_work_anniversary_reminders, | |||
) | |||
emp = make_employee( | |||
"test_emp_work_anniversary@gmail.com", | |||
company="_Test Company", | |||
date_of_joining=frappe.utils.add_years(getdate(), -2), | |||
) | |||
employees_having_work_anniversary = get_employees_having_an_event_today("work_anniversary") | |||
employees = employees_having_work_anniversary.get("_Test Company") or [] | |||
user_ids = [] | |||
for entry in employees: | |||
user_ids.append(entry.user_id) | |||
self.assertTrue("test_emp_work_anniversary@gmail.com" in user_ids) | |||
hr_settings = frappe.get_doc("HR Settings", "HR Settings") | |||
hr_settings.send_work_anniversary_reminders = 1 | |||
hr_settings.save() | |||
send_work_anniversary_reminders() | |||
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) | |||
self.assertTrue("Subject: Work Anniversary Reminder" in email_queue[0].message) | |||
def test_work_anniversary_reminder_not_sent_for_0_years(self): | |||
make_employee( | |||
"test_work_anniversary_2@gmail.com", | |||
date_of_joining=getdate(), | |||
company="_Test Company", | |||
) | |||
from hrms.controllers.employee_reminders import get_employees_having_an_event_today | |||
employees_having_work_anniversary = get_employees_having_an_event_today("work_anniversary") | |||
employees = employees_having_work_anniversary.get("_Test Company") or [] | |||
user_ids = [] | |||
for entry in employees: | |||
user_ids.append(entry.user_id) | |||
self.assertTrue("test_work_anniversary_2@gmail.com" not in user_ids) | |||
def test_send_holidays_reminder_in_advance(self): | |||
setup_hr_settings("Weekly") | |||
holidays = get_holidays_for_employee( | |||
self.test_employee.get("name"), | |||
getdate(), | |||
getdate() + timedelta(days=3), | |||
only_non_weekly=True, | |||
raise_exception=False, | |||
) | |||
send_holidays_reminder_in_advance(self.test_employee.get("name"), holidays) | |||
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) | |||
self.assertEqual(len(email_queue), 1) | |||
self.assertTrue("Holidays this Week." in email_queue[0].message) | |||
def test_advance_holiday_reminders_monthly(self): | |||
from hrms.controllers.employee_reminders import send_reminders_in_advance_monthly | |||
setup_hr_settings("Monthly") | |||
# disable emp 2, set same holiday list | |||
frappe.db.set_value( | |||
"Employee", | |||
self.test_employee_2.name, | |||
{"status": "Left", "holiday_list": self.test_employee.holiday_list}, | |||
) | |||
send_reminders_in_advance_monthly() | |||
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) | |||
self.assertTrue(len(email_queue) > 0) | |||
# even though emp 2 has holiday, non-active employees should not be recipients | |||
recipients = frappe.db.get_all("Email Queue Recipient", pluck="recipient") | |||
self.assertTrue(self.test_employee_2.user_id not in recipients) | |||
# teardown: enable emp 2 | |||
frappe.db.set_value( | |||
"Employee", | |||
self.test_employee_2.name, | |||
{"status": "Active", "holiday_list": self.holiday_list_2.name}, | |||
) | |||
def test_advance_holiday_reminders_weekly(self): | |||
from hrms.controllers.employee_reminders import send_reminders_in_advance_weekly | |||
setup_hr_settings("Weekly") | |||
# disable emp 2, set same holiday list | |||
frappe.db.set_value( | |||
"Employee", | |||
self.test_employee_2.name, | |||
{"status": "Left", "holiday_list": self.test_employee.holiday_list}, | |||
) | |||
send_reminders_in_advance_weekly() | |||
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) | |||
self.assertTrue(len(email_queue) > 0) | |||
# even though emp 2 has holiday, non-active employees should not be recipients | |||
recipients = frappe.db.get_all("Email Queue Recipient", pluck="recipient") | |||
self.assertTrue(self.test_employee_2.user_id not in recipients) | |||
# teardown: enable emp 2 | |||
frappe.db.set_value( | |||
"Employee", | |||
self.test_employee_2.name, | |||
{"status": "Active", "holiday_list": self.holiday_list_2.name}, | |||
) | |||
def test_reminder_not_sent_if_no_holdays(self): | |||
setup_hr_settings("Monthly") | |||
# reminder not sent if there are no holidays | |||
holidays = get_holidays_for_employee( | |||
self.test_employee_2.get("name"), | |||
getdate(), | |||
getdate() + timedelta(days=3), | |||
only_non_weekly=True, | |||
raise_exception=False, | |||
) | |||
send_holidays_reminder_in_advance(self.test_employee_2.get("name"), holidays) | |||
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) | |||
self.assertEqual(len(email_queue), 0) | |||
def setup_hr_settings(frequency=None): | |||
# Get HR settings and enable advance holiday reminders | |||
hr_settings = frappe.get_doc("HR Settings", "HR Settings") | |||
hr_settings.send_holiday_reminders = 1 | |||
set_proceed_with_frequency_change() | |||
hr_settings.frequency = frequency or "Weekly" | |||
hr_settings.save() |
@@ -0,0 +1,301 @@ | |||
app_name = "hrms" | |||
app_title = "Frappe HR" | |||
app_publisher = "Frappe Technologies Pvt. Ltd." | |||
app_description = "Modern HR and Payroll Software" | |||
app_email = "contact@frappe.io" | |||
app_license = "GNU General Public License (v3)" | |||
required_apps = ["erpnext"] | |||
# Includes in <head> | |||
# ------------------ | |||
# include js, css files in header of desk.html | |||
# app_include_css = "/assets/hrms/css/hrms.css" | |||
# app_include_js = "/assets/hrms/js/hrms.js" | |||
# include js, css files in header of web template | |||
# web_include_css = "/assets/hrms/css/hrms.css" | |||
# web_include_js = "/assets/hrms/js/hrms.js" | |||
# include custom scss in every website theme (without file extension ".scss") | |||
# website_theme_scss = "hrms/public/scss/website" | |||
# include js, css files in header of web form | |||
# webform_include_js = {"doctype": "public/js/doctype.js"} | |||
# webform_include_css = {"doctype": "public/css/doctype.css"} | |||
# include js in page | |||
# page_js = {"page" : "public/js/file.js"} | |||
# include js in doctype views | |||
doctype_js = { | |||
"Employee": "public/js/employee.js", | |||
"Company": "public/js/company.js", | |||
"Department": "public/js/department.js", | |||
"Timesheet": "public/js/timesheet.js", | |||
"Payment Entry": "public/js/payment_entry.js", | |||
"Journal Entry": "public/js/journal_entry.js", | |||
"Delivery Trip": "public/js/deliver_trip.js", | |||
"Bank Transaction": "public/js/bank_transaction.js", | |||
} | |||
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} | |||
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} | |||
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} | |||
# Home Pages | |||
# ---------- | |||
# application home page (will override Website Settings) | |||
# home_page = "login" | |||
# website user home page (by Role) | |||
# role_home_page = { | |||
# "Role": "home_page" | |||
# } | |||
# Generators | |||
# ---------- | |||
# automatically create page for each record of this doctype | |||
website_generators = ["Job Opening"] | |||
website_route_rules = [ | |||
{"from_route": "/jobs", "to_route": "Job Opening"}, | |||
] | |||
# Jinja | |||
# ---------- | |||
# add methods and filters to jinja environment | |||
jinja = { | |||
"methods": [ | |||
"hrms.utils.get_country", | |||
], | |||
} | |||
# Installation | |||
# ------------ | |||
# before_install = "hrms.install.before_install" | |||
after_install = "hrms.setup.after_install" | |||
after_migrate = ["hrms.setup.update_select_perm_after_install"] | |||
# Uninstallation | |||
# ------------ | |||
# before_uninstall = "hrms.uninstall.before_uninstall" | |||
# after_uninstall = "hrms.uninstall.after_uninstall" | |||
# Desk Notifications | |||
# ------------------ | |||
# See frappe.core.notifications.get_notification_config | |||
# notification_config = "hrms.notifications.get_notification_config" | |||
# Permissions | |||
# ----------- | |||
# Permissions evaluated in scripted ways | |||
# permission_query_conditions = { | |||
# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", | |||
# } | |||
# | |||
# has_permission = { | |||
# "Event": "frappe.desk.doctype.event.event.has_permission", | |||
# } | |||
has_upload_permission = { | |||
"Employee": "erpnext.setup.doctype.employee.employee.has_upload_permission" | |||
} | |||
# DocType Class | |||
# --------------- | |||
# Override standard doctype classes | |||
override_doctype_class = { | |||
"Employee": "hrms.overrides.employee_master.EmployeeMaster", | |||
"Timesheet": "hrms.overrides.employee_timesheet.EmployeeTimesheet", | |||
"Payment Entry": "hrms.overrides.employee_payment_entry.EmployeePaymentEntry", | |||
"Project": "hrms.overrides.employee_project.EmployeeProject", | |||
} | |||
# Document Events | |||
# --------------- | |||
# Hook on document methods and events | |||
doc_events = { | |||
"User": { | |||
"validate": "erpnext.setup.doctype.employee.employee.validate_employee_role", | |||
"on_update": "erpnext.setup.doctype.employee.employee.update_user_permissions", | |||
}, | |||
"Company": { | |||
"validate": "hrms.overrides.company.validate_default_accounts", | |||
"on_update": [ | |||
"hrms.overrides.company.make_company_fixtures", | |||
"hrms.overrides.company.set_default_hr_accounts", | |||
], | |||
}, | |||
"Timesheet": {"validate": "hrms.hr.utils.validate_active_employee"}, | |||
"Payment Entry": { | |||
"on_submit": "hrms.hr.doctype.expense_claim.expense_claim.update_payment_for_expense_claim", | |||
"on_cancel": "hrms.hr.doctype.expense_claim.expense_claim.update_payment_for_expense_claim", | |||
}, | |||
"Journal Entry": { | |||
"validate": "hrms.hr.doctype.expense_claim.expense_claim.validate_expense_claim_in_jv", | |||
"on_submit": [ | |||
"hrms.hr.doctype.expense_claim.expense_claim.update_payment_for_expense_claim", | |||
"hrms.hr.doctype.full_and_final_statement.full_and_final_statement.update_full_and_final_statement_status", | |||
], | |||
"on_cancel": [ | |||
"hrms.hr.doctype.expense_claim.expense_claim.update_payment_for_expense_claim", | |||
"hrms.payroll.doctype.salary_slip.salary_slip.unlink_ref_doc_from_salary_slip", | |||
"hrms.hr.doctype.full_and_final_statement.full_and_final_statement.update_full_and_final_statement_status", | |||
], | |||
}, | |||
"Loan": {"validate": "hrms.hr.utils.validate_loan_repay_from_salary"}, | |||
"Employee": { | |||
"validate": [ | |||
"hrms.overrides.employee_master.validate_onboarding_process", | |||
"hrms.overrides.employee_master.update_to_date_in_work_history", | |||
], | |||
"on_update": "hrms.overrides.employee_master.update_approver_role", | |||
"on_trash": "hrms.overrides.employee_master.update_employee_transfer", | |||
}, | |||
"Project": { | |||
"validate": "hrms.controllers.employee_boarding_controller.update_employee_boarding_status" | |||
}, | |||
"Task": {"on_update": "hrms.controllers.employee_boarding_controller.update_task"}, | |||
} | |||
# Scheduled Tasks | |||
# --------------- | |||
scheduler_events = { | |||
"all": [ | |||
"hrms.hr.doctype.interview.interview.send_interview_reminder", | |||
], | |||
"hourly": [ | |||
"hrms.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails", | |||
"hrms.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", | |||
], | |||
"daily": [ | |||
"hrms.controllers.employee_reminders.send_birthday_reminders", | |||
"hrms.controllers.employee_reminders.send_work_anniversary_reminders", | |||
"hrms.hr.doctype.daily_work_summary_group.daily_work_summary_group.send_summary", | |||
"hrms.hr.doctype.interview.interview.send_daily_feedback_reminder", | |||
], | |||
"daily_long": [ | |||
"hrms.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation", | |||
"hrms.hr.utils.generate_leave_encashment", | |||
"hrms.hr.utils.allocate_earned_leaves", | |||
], | |||
"weekly": ["hrms.controllers.employee_reminders.send_reminders_in_advance_weekly"], | |||
"monthly": ["hrms.controllers.employee_reminders.send_reminders_in_advance_monthly"], | |||
} | |||
advance_payment_doctypes = ["Gratuity", "Employee Advance"] | |||
invoice_doctypes = ["Expense Claim"] | |||
period_closing_doctypes = ["Payroll Entry"] | |||
accounting_dimension_doctypes = [ | |||
"Expense Claim", | |||
"Expense Claim Detail", | |||
"Expense Taxes and Charges", | |||
"Payroll Entry", | |||
] | |||
bank_reconciliation_doctypes = ["Expense Claim"] | |||
# Testing | |||
# ------- | |||
before_tests = "hrms.utils.before_tests" | |||
# Overriding Methods | |||
# ----------------------------- | |||
# get matching queries for Bank Reconciliation | |||
get_matching_queries = "hrms.hr.utils.get_matching_queries" | |||
regional_overrides = { | |||
"India": { | |||
"hrms.hr.utils.calculate_annual_eligible_hra_exemption": "hrms.regional.india.utils.calculate_annual_eligible_hra_exemption", | |||
"hrms.hr.utils.calculate_hra_exemption_for_period": "hrms.regional.india.utils.calculate_hra_exemption_for_period", | |||
}, | |||
} | |||
# ERPNext doctypes for Global Search | |||
global_search_doctypes = { | |||
"Default": [ | |||
{"doctype": "Salary Slip", "index": 19}, | |||
{"doctype": "Leave Application", "index": 20}, | |||
{"doctype": "Expense Claim", "index": 21}, | |||
{"doctype": "Employee Grade", "index": 37}, | |||
{"doctype": "Job Opening", "index": 39}, | |||
{"doctype": "Job Applicant", "index": 40}, | |||
{"doctype": "Job Offer", "index": 41}, | |||
{"doctype": "Salary Structure Assignment", "index": 42}, | |||
{"doctype": "Appraisal", "index": 43}, | |||
], | |||
} | |||
# override_whitelisted_methods = { | |||
# "frappe.desk.doctype.event.event.get_events": "hrms.event.get_events" | |||
# } | |||
# | |||
# each overriding function accepts a `data` argument; | |||
# generated from the base implementation of the doctype dashboard, | |||
# along with any modifications made in other Frappe apps | |||
override_doctype_dashboards = { | |||
"Employee": "hrms.overrides.dashboard_overrides.get_dashboard_for_employee", | |||
"Holiday List": "hrms.overrides.dashboard_overrides.get_dashboard_for_holiday_list", | |||
"Task": "hrms.overrides.dashboard_overrides.get_dashboard_for_project", | |||
"Project": "hrms.overrides.dashboard_overrides.get_dashboard_for_project", | |||
"Timesheet": "hrms.overrides.dashboard_overrides.get_dashboard_for_timesheet", | |||
} | |||
# exempt linked doctypes from being automatically cancelled | |||
# | |||
# auto_cancel_exempted_doctypes = ["Auto Repeat"] | |||
# User Data Protection | |||
# -------------------- | |||
# user_data_fields = [ | |||
# { | |||
# "doctype": "{doctype_1}", | |||
# "filter_by": "{filter_by}", | |||
# "redact_fields": ["{field_1}", "{field_2}"], | |||
# "partial": 1, | |||
# }, | |||
# { | |||
# "doctype": "{doctype_2}", | |||
# "filter_by": "{filter_by}", | |||
# "partial": 1, | |||
# }, | |||
# { | |||
# "doctype": "{doctype_3}", | |||
# "strict": False, | |||
# }, | |||
# { | |||
# "doctype": "{doctype_4}" | |||
# } | |||
# ] | |||
# Authentication and authorization | |||
# -------------------------------- | |||
# auth_hooks = [ | |||
# "hrms.auth.validate" | |||
# ] | |||
# Translation | |||
# -------------------------------- | |||
# Make link fields search translated document names for these DocTypes | |||
# Recommended only for DocTypes which have limited documents with untranslated names | |||
# For example: Role, Gender, etc. | |||
# translated_search_doctypes = [] |
@@ -0,0 +1,6 @@ | |||
Key features: | |||
- Leave and Attendance | |||
- Payroll | |||
- Appraisal | |||
- Expense Claim |
@@ -0,0 +1,28 @@ | |||
{ | |||
"chart_name": "Attendance Count", | |||
"chart_type": "Report", | |||
"creation": "2020-07-22 11:56:32.730068", | |||
"custom_options": "{\n\t\t\"type\": \"line\",\n\t\t\"axisOptions\": {\n\t\t\t\"shortenYAxisNumbers\": 1\n\t\t},\n\t\t\"tooltipOptions\": {}\n\t}", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"dynamic_filters_json": "{\"month\":\"frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1\",\"year\":\"frappe.datetime.str_to_obj(frappe.datetime.get_today()).getFullYear();\",\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}", | |||
"filters_json": "{\"summarized_view\":0}", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"modified": "2022-08-21 14:25:01.608941", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Attendance Count", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"report_name": "Monthly Attendance Sheet", | |||
"roles": [], | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Line", | |||
"use_report_chart": 1, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Claims by Type", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-31 23:04:43.377345", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Expense Claim Detail", | |||
"dynamic_filters_json": "[]", | |||
"filters_json": "[]", | |||
"group_by_based_on": "expense_type", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-09-16 11:36:29.484579", | |||
"modified": "2022-09-16 11:39:08.205987", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Claims by Type", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "Expense Claim", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,30 @@ | |||
{ | |||
"chart_name": "Department Wise Employee Count", | |||
"chart_type": "Group By", | |||
"creation": "2020-07-22 11:56:32.760730", | |||
"custom_options": "", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee", | |||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", | |||
"group_by_based_on": "department", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 14:08:17.017113", | |||
"modified": "2022-08-22 14:09:19.603767", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Department Wise Employee Count", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"roles": [], | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Department wise Expense Claims", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-31 23:06:51.144716", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Expense Claim", | |||
"dynamic_filters_json": "[[\"Expense Claim\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Expense Claim\",\"docstatus\",\"=\",\"1\",false]]", | |||
"group_by_based_on": "department", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-09-16 12:36:29.444007", | |||
"modified": "2022-09-16 11:41:32.160907", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Department wise Expense Claims", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Bar", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,31 @@ | |||
{ | |||
"aggregate_function_based_on": "planned_vacancies", | |||
"chart_name": "Department Wise Openings", | |||
"chart_type": "Group By", | |||
"creation": "2020-07-22 11:56:32.849775", | |||
"custom_options": "", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Job Opening", | |||
"dynamic_filters_json": "[[\"Job Opening\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Job Opening\",\"status\",\"=\",\"Open\",false]]", | |||
"group_by_based_on": "department", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-21 23:19:31.637348", | |||
"modified": "2022-08-20 23:22:24.707871", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Department Wise Openings", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"roles": [], | |||
"time_interval": "Monthly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Bar", | |||
"use_report_chart": 0, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,34 @@ | |||
{ | |||
"aggregate_function_based_on": "total_hours", | |||
"based_on": "", | |||
"chart_name": "Department wise Timesheet Hours", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-21 17:32:09.625319", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Timesheet", | |||
"dynamic_filters_json": "[[\"Timesheet\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Timesheet\",\"start_date\",\"Timespan\",\"this month\",false],[\"Timesheet\",\"docstatus\",\"=\",\"1\",false]]", | |||
"group_by_based_on": "department", | |||
"group_by_type": "Sum", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-21 17:56:03.184928", | |||
"modified": "2022-08-21 17:57:47.234034", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Department wise Timesheet Hours", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Bar", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,30 @@ | |||
{ | |||
"chart_name": "Designation Wise Employee Count", | |||
"chart_type": "Group By", | |||
"creation": "2020-07-22 11:56:32.790337", | |||
"custom_options": "", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee", | |||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", | |||
"group_by_based_on": "designation", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 12:33:54.631648", | |||
"modified": "2022-08-22 12:45:29.009857", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Designation Wise Employee Count", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"roles": [], | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,31 @@ | |||
{ | |||
"aggregate_function_based_on": "planned_vacancies", | |||
"chart_name": "Designation Wise Openings", | |||
"chart_type": "Group By", | |||
"creation": "2020-07-22 11:56:32.820217", | |||
"custom_options": "", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Job Opening", | |||
"dynamic_filters_json": "[[\"Job Opening\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Job Opening\",\"status\",\"=\",\"Open\",false]]", | |||
"group_by_based_on": "designation", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-20 23:20:41.683553", | |||
"modified": "2022-08-20 23:22:15.254254", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Designation Wise Openings", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"roles": [], | |||
"time_interval": "Monthly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Bar", | |||
"use_report_chart": 0, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Employee Advance Status", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-31 23:06:16.063039", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee Advance", | |||
"dynamic_filters_json": "[[\"Employee Advance\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee Advance\",\"docstatus\",\"=\",\"1\",false]]", | |||
"group_by_based_on": "status", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-09-16 12:36:29.430589", | |||
"modified": "2022-09-16 11:43:57.244256", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Employee Advance Status", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Employees by Age", | |||
"chart_type": "Custom", | |||
"creation": "2022-08-22 19:07:51.906347", | |||
"custom_options": "{\n\t\"colors\": [\"#7cd6fd\"],\n\t\"barOptions\": {\"spaceRatio\": 0.5}\n}", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "", | |||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}", | |||
"filters_json": "{}", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 19:00:02.464180", | |||
"modified": "2022-08-22 19:11:20.076166", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Employees by Age", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "Employees by Age", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Bar", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,30 @@ | |||
{ | |||
"chart_name": "Employees by Branch", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-22 12:33:43.241006", | |||
"custom_options": "", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee", | |||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", | |||
"group_by_based_on": "branch", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 12:25:47.733581", | |||
"modified": "2022-08-22 12:33:49.094799", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Employees by Branch", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"roles": [], | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,30 @@ | |||
{ | |||
"chart_name": "Employees by Grade", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-22 12:33:23.767559", | |||
"custom_options": "", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee", | |||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", | |||
"group_by_based_on": "grade", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 12:25:47.733581", | |||
"modified": "2022-08-22 12:33:29.562836", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Employees by Grade", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"roles": [], | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,30 @@ | |||
{ | |||
"chart_name": "Employees by Type", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-22 13:49:59.343893", | |||
"custom_options": "", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee", | |||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", | |||
"group_by_based_on": "employment_type", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 13:45:38.913766", | |||
"modified": "2022-08-22 13:50:05.645535", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Employees by Type", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"roles": [], | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "posting_date", | |||
"chart_name": "Expense Claims", | |||
"chart_type": "Sum", | |||
"color": "#449CF0", | |||
"creation": "2022-08-21 14:07:24.120739", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Expense Claim", | |||
"dynamic_filters_json": "[[\"Expense Claim\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Expense Claim\",\"docstatus\",\"=\",\"1\",false]]", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-09-16 11:36:29.292891", | |||
"modified": "2022-09-16 11:37:59.669291", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Expense Claims", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Monthly", | |||
"timeseries": 1, | |||
"timespan": "Last Year", | |||
"type": "Line", | |||
"use_report_chart": 0, | |||
"value_based_on": "total_sanctioned_amount", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,29 @@ | |||
{ | |||
"chart_name": "Gender Diversity Ratio", | |||
"chart_type": "Group By", | |||
"creation": "2020-07-22 11:56:32.667291", | |||
"custom_options": "", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee", | |||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]", | |||
"group_by_based_on": "gender", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2020-07-22 14:27:40.143783", | |||
"modified": "2020-07-22 14:32:50.962459", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Gender Diversity Ratio", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Grievance Type", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-21 13:02:06.880100", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee Grievance", | |||
"dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee Grievance\",\"docstatus\",\"=\",\"1\",false]]", | |||
"group_by_based_on": "grievance_type", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-21 13:08:57.019388", | |||
"modified": "2022-08-21 13:40:40.415600", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Grievance Type", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Hiring vs Attrition Count", | |||
"chart_type": "Custom", | |||
"creation": "2022-08-21 22:58:12.740936", | |||
"custom_options": "{\n\t\"type\": \"axis-mixed\",\n\t\"axisOptions\": {\n\t\t\"xIsSeries\": 1\n\t},\n\t\"lineOptions\": {\n\t \"regionFill\": 1\n\t},\n\t\"colors\": [\"#7cd6fd\", \"#5e64ff\"]\n}", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "", | |||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\", \"from_date\":\"frappe.defaults.get_user_default(\\\"year_start_date\\\")\", \"to_date\":\"frappe.defaults.get_user_default(\\\"year_end_date\\\")\"}", | |||
"filters_json": "{\"time_interval\":\"Monthly\"}", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 10:57:55.011020", | |||
"modified": "2022-08-22 11:03:30.080835", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Hiring vs Attrition Count", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "Hiring vs Attrition Count", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Line", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Interview Status", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-20 23:10:33.131622", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Interview", | |||
"dynamic_filters_json": "[]", | |||
"filters_json": "[]", | |||
"group_by_based_on": "status", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 12:13:19.640093", | |||
"modified": "2022-08-22 12:16:33.674218", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Interview Status", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Job Applicant Pipeline", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-20 21:18:45.283444", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Job Applicant", | |||
"dynamic_filters_json": "[]", | |||
"filters_json": "[]", | |||
"group_by_based_on": "job_title", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-20 23:45:11.740188", | |||
"modified": "2022-08-20 23:48:35.499218", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Job Applicant Pipeline", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Percentage", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Job Applicant Source", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-20 22:59:15.210760", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Job Applicant", | |||
"dynamic_filters_json": "[]", | |||
"filters_json": "[]", | |||
"group_by_based_on": "source", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-20 23:45:11.697841", | |||
"modified": "2022-08-20 23:47:52.946872", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Job Applicant Source", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Percentage", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,32 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Job Applicants by Country", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-22 12:17:53.776473", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Job Applicant", | |||
"dynamic_filters_json": "[]", | |||
"filters_json": "[]", | |||
"group_by_based_on": "country", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"modified": "2022-08-22 12:18:01.288634", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Job Applicants by Country", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,32 @@ | |||
{ | |||
"based_on": "creation", | |||
"chart_name": "Job Application Frequency", | |||
"chart_type": "Count", | |||
"creation": "2022-08-20 22:00:12.227849", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Job Applicant", | |||
"dynamic_filters_json": "[]", | |||
"filters_json": "[]", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-20 23:11:18.520971", | |||
"modified": "2022-08-20 23:16:02.076184", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Job Application Frequency", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Monthly", | |||
"timeseries": 1, | |||
"timespan": "Last Year", | |||
"type": "Line", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,30 @@ | |||
{ | |||
"chart_name": "Job Application Status", | |||
"chart_type": "Group By", | |||
"creation": "2020-07-22 11:56:32.699696", | |||
"custom_options": "", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Job Applicant", | |||
"dynamic_filters_json": "", | |||
"filters_json": "[]", | |||
"group_by_based_on": "status", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 12:07:53.129240", | |||
"modified": "2022-08-22 12:10:29.144396", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Job Application Status", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"roles": [], | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,33 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Job Offer Status", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-20 21:33:17.378147", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Job Offer", | |||
"dynamic_filters_json": "[]", | |||
"filters_json": "[]", | |||
"group_by_based_on": "status", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-22 12:12:19.736710", | |||
"modified": "2022-08-22 12:14:23.044346", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Job Offer Status", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,32 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Shift Assignment Breakup", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-21 18:11:42.510195", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Shift Assignment", | |||
"dynamic_filters_json": "[[\"Shift Assignment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Shift Assignment\",\"docstatus\",\"=\",\"1\",false]]", | |||
"group_by_based_on": "shift_type", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"modified": "2022-08-21 18:12:47.352410", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Shift Assignment Breakup", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,34 @@ | |||
{ | |||
"aggregate_function_based_on": "hours", | |||
"based_on": "", | |||
"chart_name": "Timesheet Activity Breakup", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-21 14:31:10.401241", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Timesheet Detail", | |||
"dynamic_filters_json": "[]", | |||
"filters_json": "[]", | |||
"group_by_based_on": "activity_type", | |||
"group_by_type": "Sum", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"last_synced_on": "2022-08-21 17:55:44.318686", | |||
"modified": "2022-08-21 17:59:38.576219", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Timesheet Activity Breakup", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "Timesheet", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Percentage", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,32 @@ | |||
{ | |||
"based_on": "", | |||
"chart_name": "Training Type", | |||
"chart_type": "Group By", | |||
"creation": "2022-08-21 13:29:27.202404", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Training Event", | |||
"dynamic_filters_json": "[]", | |||
"filters_json": "[[\"Training Event\",\"docstatus\",\"=\",\"1\",false]]", | |||
"group_by_based_on": "type", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"modified": "2022-08-21 13:38:05.390856", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Training Type", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 0, | |||
"timespan": "Last Year", | |||
"type": "Pie", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,31 @@ | |||
{ | |||
"based_on": "promotion_date", | |||
"chart_name": "Y-O-Y Promotions", | |||
"chart_type": "Count", | |||
"creation": "2022-08-21 13:34:12.830736", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee Promotion", | |||
"dynamic_filters_json": "[[\"Employee Promotion\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee Promotion\",\"docstatus\",\"=\",\"1\",false]]", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"modified": "2022-08-21 13:38:46.190352", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Y-O-Y Promotions", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 1, | |||
"timespan": "Last Year", | |||
"type": "Line", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,31 @@ | |||
{ | |||
"based_on": "transfer_date", | |||
"chart_name": "Y-O-Y Transfers", | |||
"chart_type": "Count", | |||
"creation": "2022-08-21 13:28:05.162754", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart", | |||
"document_type": "Employee Transfer", | |||
"dynamic_filters_json": "[[\"Employee Transfer\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", | |||
"filters_json": "[[\"Employee Transfer\",\"docstatus\",\"=\",\"1\",false]]", | |||
"group_by_type": "Count", | |||
"idx": 0, | |||
"is_public": 1, | |||
"is_standard": 1, | |||
"modified": "2022-08-21 13:38:54.890663", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Y-O-Y Transfers", | |||
"number_of_groups": 0, | |||
"owner": "Administrator", | |||
"parent_document_type": "", | |||
"roles": [], | |||
"source": "", | |||
"time_interval": "Yearly", | |||
"timeseries": 1, | |||
"timespan": "Last Year", | |||
"type": "Line", | |||
"use_report_chart": 0, | |||
"value_based_on": "", | |||
"y_axis": [] | |||
} |
@@ -0,0 +1,14 @@ | |||
frappe.provide("frappe.dashboards.chart_sources"); | |||
frappe.dashboards.chart_sources["Employees by Age"] = { | |||
method: "hrms.hr.dashboard_chart_source.employees_by_age.employees_by_age.get_data", | |||
filters: [ | |||
{ | |||
fieldname: "company", | |||
label: __("Company"), | |||
fieldtype: "Link", | |||
options: "Company", | |||
default: frappe.defaults.get_user_default("Company") | |||
}, | |||
] | |||
}; |
@@ -0,0 +1,13 @@ | |||
{ | |||
"creation": "2022-08-22 12:49:07.303139", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart Source", | |||
"idx": 0, | |||
"modified": "2022-08-22 12:49:07.303139", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Employees by Age", | |||
"owner": "Administrator", | |||
"source_name": "Employees by Age", | |||
"timeseries": 0 | |||
} |
@@ -0,0 +1,87 @@ | |||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors | |||
# License: GNU General Public License v3. See license.txt | |||
from dateutil.relativedelta import relativedelta | |||
import frappe | |||
from frappe import _ | |||
from frappe.utils import getdate | |||
from frappe.utils.dashboard import cache_source | |||
@frappe.whitelist() | |||
@cache_source | |||
def get_data( | |||
chart_name=None, | |||
chart=None, | |||
no_cache=None, | |||
filters=None, | |||
from_date=None, | |||
to_date=None, | |||
timespan=None, | |||
time_interval=None, | |||
heatmap_year=None, | |||
) -> dict[str, list]: | |||
if filters: | |||
filters = frappe.parse_json(filters) | |||
employees = frappe.db.get_list( | |||
"Employee", | |||
filters={"company": filters.get("company"), "status": "Active"}, | |||
pluck="date_of_birth", | |||
) | |||
age_list = get_age_list(employees) | |||
ranges = get_ranges() | |||
age_range, values = get_employees_by_age(age_list, ranges) | |||
return { | |||
"labels": age_range, | |||
"datasets": [ | |||
{"name": _("Employees"), "values": values}, | |||
], | |||
} | |||
def get_ranges() -> list[tuple[int, int]]: | |||
ranges = [] | |||
for i in range(15, 80, 5): | |||
ranges.append((i, i + 4)) | |||
ranges.append(80) | |||
return ranges | |||
def get_age_list(employees) -> list[int]: | |||
age_list = [] | |||
for dob in employees: | |||
if not dob: | |||
continue | |||
age = relativedelta(getdate(), getdate(dob)).years | |||
age_list.append(age) | |||
return age_list | |||
def get_employees_by_age(age_list, ranges) -> tuple[list[str], list[int]]: | |||
age_range = [] | |||
values = [] | |||
for bracket in ranges: | |||
if isinstance(bracket, int): | |||
age_range.append(f"{bracket}+") | |||
else: | |||
age_range.append(f"{bracket[0]}-{bracket[1]}") | |||
count = 0 | |||
for age in age_list: | |||
if (isinstance(bracket, int) and age >= bracket) or ( | |||
isinstance(bracket, tuple) and bracket[0] <= age <= bracket[1] | |||
): | |||
count += 1 | |||
values.append(count) | |||
return age_range, values |
@@ -0,0 +1,35 @@ | |||
frappe.provide("frappe.dashboards.chart_sources"); | |||
frappe.dashboards.chart_sources["Hiring vs Attrition Count"] = { | |||
method: "hrms.hr.dashboard_chart_source.hiring_vs_attrition_count.hiring_vs_attrition_count.get_data", | |||
filters: [ | |||
{ | |||
fieldname: "company", | |||
label: __("Company"), | |||
fieldtype: "Link", | |||
options: "Company", | |||
default: frappe.defaults.get_user_default("Company") | |||
}, | |||
{ | |||
fieldname: "from_date", | |||
label: __("From Date"), | |||
fieldtype: "Date", | |||
default: frappe.defaults.get_user_default("year_start_date"), | |||
reqd: 1, | |||
}, | |||
{ | |||
fieldname: "to_date", | |||
label: __("To Date"), | |||
fieldtype: "Date", | |||
default: frappe.defaults.get_user_default("year_end_date"), | |||
}, | |||
{ | |||
fieldname: "time_interval", | |||
label: __("Time Interval"), | |||
fieldtype: "Select", | |||
options: ["Monthly", "Quarterly", "Yearly"], | |||
default: "Monthly", | |||
reqd: 1 | |||
}, | |||
] | |||
}; |
@@ -0,0 +1,13 @@ | |||
{ | |||
"creation": "2022-08-21 21:38:22.271985", | |||
"docstatus": 0, | |||
"doctype": "Dashboard Chart Source", | |||
"idx": 0, | |||
"modified": "2022-08-21 21:38:22.271985", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Hiring vs Attrition Count", | |||
"owner": "Administrator", | |||
"source_name": "Hiring vs Attrition Count", | |||
"timeseries": 1 | |||
} |
@@ -0,0 +1,69 @@ | |||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors | |||
# License: GNU General Public License v3. See license.txt | |||
import frappe | |||
from frappe import _ | |||
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result | |||
from frappe.utils import getdate | |||
from frappe.utils.dashboard import cache_source | |||
from frappe.utils.dateutils import get_period | |||
@frappe.whitelist() | |||
@cache_source | |||
def get_data( | |||
chart_name=None, | |||
chart=None, | |||
no_cache=None, | |||
filters=None, | |||
from_date=None, | |||
to_date=None, | |||
timespan=None, | |||
time_interval=None, | |||
heatmap_year=None, | |||
) -> dict[str, list]: | |||
if filters: | |||
filters = frappe.parse_json(filters) | |||
from_date = filters.get("from_date") | |||
to_date = filters.get("to_date") | |||
if not to_date: | |||
to_date = getdate() | |||
hiring = get_records(from_date, to_date, "date_of_joining", filters.get("company")) | |||
attrition = get_records(from_date, to_date, "relieving_date", filters.get("company")) | |||
hiring_data = get_result(hiring, filters.get("time_interval"), from_date, to_date, "Count") | |||
attrition_data = get_result(attrition, filters.get("time_interval"), from_date, to_date, "Count") | |||
return { | |||
"labels": [get_period(r[0], filters.get("time_interval")) for r in hiring_data], | |||
"datasets": [ | |||
{"name": _("Hiring Count"), "values": [r[1] for r in hiring_data]}, | |||
{"name": _("Attrition Count"), "values": [r[1] for r in attrition_data]}, | |||
], | |||
} | |||
def get_records( | |||
from_date: str, to_date: str, datefield: str, company: str | |||
) -> tuple[tuple[str, float, int]]: | |||
filters = [ | |||
["Employee", "company", "=", company], | |||
["Employee", datefield, ">=", from_date, False], | |||
["Employee", datefield, "<=", to_date, False], | |||
] | |||
data = frappe.db.get_list( | |||
"Employee", | |||
fields=[f"{datefield} as _unit", "SUM(1)", "COUNT(*)"], | |||
filters=filters, | |||
group_by="_unit", | |||
order_by="_unit asc", | |||
as_list=True, | |||
ignore_ifnull=True, | |||
) | |||
return data |
@@ -0,0 +1,30 @@ | |||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors | |||
// For license information, please see license.txt | |||
frappe.ui.form.on('Appointment Letter', { | |||
appointment_letter_template: function(frm){ | |||
if (frm.doc.appointment_letter_template){ | |||
frappe.call({ | |||
method: 'hrms.hr.doctype.appointment_letter.appointment_letter.get_appointment_letter_details', | |||
args : { | |||
template : frm.doc.appointment_letter_template | |||
}, | |||
callback: function(r){ | |||
if(r.message){ | |||
let message_body = r.message; | |||
frm.set_value("introduction", message_body[0].introduction); | |||
frm.set_value("closing_notes", message_body[0].closing_notes); | |||
frm.doc.terms = [] | |||
for (var i in message_body[1].description){ | |||
frm.add_child("terms"); | |||
frm.fields_dict.terms.get_value()[i].title = message_body[1].description[i].title; | |||
frm.fields_dict.terms.get_value()[i].description = message_body[1].description[i].description; | |||
} | |||
frm.refresh(); | |||
} | |||
} | |||
}); | |||
} | |||
}, | |||
}); |
@@ -0,0 +1,128 @@ | |||
{ | |||
"actions": [], | |||
"autoname": "HR-APP-LETTER-.#####", | |||
"creation": "2019-12-26 12:35:49.574828", | |||
"default_print_format": "Standard Appointment Letter", | |||
"doctype": "DocType", | |||
"editable_grid": 1, | |||
"engine": "InnoDB", | |||
"field_order": [ | |||
"job_applicant", | |||
"applicant_name", | |||
"column_break_3", | |||
"company", | |||
"appointment_date", | |||
"appointment_letter_template", | |||
"body_section", | |||
"introduction", | |||
"terms", | |||
"closing_notes" | |||
], | |||
"fields": [ | |||
{ | |||
"fetch_from": "job_applicant.applicant_name", | |||
"fieldname": "applicant_name", | |||
"fieldtype": "Data", | |||
"in_global_search": 1, | |||
"in_list_view": 1, | |||
"label": "Applicant Name", | |||
"read_only": 1, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "appointment_date", | |||
"fieldtype": "Date", | |||
"label": "Appointment Date", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "appointment_letter_template", | |||
"fieldtype": "Link", | |||
"label": "Appointment Letter Template", | |||
"options": "Appointment Letter Template", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fetch_from": "appointment_letter_template.introduction", | |||
"fieldname": "introduction", | |||
"fieldtype": "Long Text", | |||
"label": "Introduction", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "body_section", | |||
"fieldtype": "Section Break", | |||
"label": "Body" | |||
}, | |||
{ | |||
"fieldname": "column_break_3", | |||
"fieldtype": "Column Break" | |||
}, | |||
{ | |||
"fieldname": "job_applicant", | |||
"fieldtype": "Link", | |||
"label": "Job Applicant", | |||
"options": "Job Applicant", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "company", | |||
"fieldtype": "Link", | |||
"label": "Company", | |||
"options": "Company", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "closing_notes", | |||
"fieldtype": "Text", | |||
"label": "Closing Notes" | |||
}, | |||
{ | |||
"fieldname": "terms", | |||
"fieldtype": "Table", | |||
"label": "Terms", | |||
"options": "Appointment Letter content", | |||
"reqd": 1 | |||
} | |||
], | |||
"links": [], | |||
"modified": "2022-01-18 19:27:35.649424", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Appointment Letter", | |||
"name_case": "Title Case", | |||
"naming_rule": "Expression (old style)", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": 1, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"write": 1 | |||
}, | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": 1, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "HR Manager", | |||
"share": 1, | |||
"write": 1 | |||
} | |||
], | |||
"search_fields": "applicant_name, company", | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"states": [], | |||
"title_field": "applicant_name", | |||
"track_changes": 1 | |||
} |
@@ -0,0 +1,29 @@ | |||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors | |||
# For license information, please see license.txt | |||
import frappe | |||
from frappe.model.document import Document | |||
class AppointmentLetter(Document): | |||
pass | |||
@frappe.whitelist() | |||
def get_appointment_letter_details(template): | |||
body = [] | |||
intro = frappe.get_list( | |||
"Appointment Letter Template", | |||
fields=["introduction", "closing_notes"], | |||
filters={"name": template}, | |||
)[0] | |||
content = frappe.get_all( | |||
"Appointment Letter content", | |||
fields=["title", "description"], | |||
filters={"parent": template}, | |||
order_by="idx", | |||
) | |||
body.append(intro) | |||
body.append({"description": content}) | |||
return body |
@@ -0,0 +1,9 @@ | |||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
# import frappe | |||
import unittest | |||
class TestAppointmentLetter(unittest.TestCase): | |||
pass |
@@ -0,0 +1,39 @@ | |||
{ | |||
"actions": [], | |||
"creation": "2019-12-26 12:22:16.575767", | |||
"doctype": "DocType", | |||
"editable_grid": 1, | |||
"engine": "InnoDB", | |||
"field_order": [ | |||
"title", | |||
"description" | |||
], | |||
"fields": [ | |||
{ | |||
"fieldname": "title", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Title", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "description", | |||
"fieldtype": "Long Text", | |||
"in_list_view": 1, | |||
"label": "Description", | |||
"reqd": 1 | |||
} | |||
], | |||
"istable": 1, | |||
"links": [], | |||
"modified": "2019-12-26 12:24:09.824084", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Appointment Letter content", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"quick_entry": 1, | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"track_changes": 1 | |||
} |
@@ -0,0 +1,10 @@ | |||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors | |||
# For license information, please see license.txt | |||
# import frappe | |||
from frappe.model.document import Document | |||
class AppointmentLettercontent(Document): | |||
pass |
@@ -0,0 +1,8 @@ | |||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors | |||
// For license information, please see license.txt | |||
frappe.ui.form.on('Appointment Letter Template', { | |||
// refresh: function(frm) { | |||
// } | |||
}); |
@@ -0,0 +1,81 @@ | |||
{ | |||
"actions": [], | |||
"autoname": "field:template_name", | |||
"creation": "2019-12-26 12:20:14.219578", | |||
"doctype": "DocType", | |||
"editable_grid": 1, | |||
"engine": "InnoDB", | |||
"field_order": [ | |||
"template_name", | |||
"introduction", | |||
"terms", | |||
"closing_notes" | |||
], | |||
"fields": [ | |||
{ | |||
"fieldname": "introduction", | |||
"fieldtype": "Long Text", | |||
"in_list_view": 1, | |||
"label": "Introduction", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "closing_notes", | |||
"fieldtype": "Text", | |||
"label": "Closing Notes" | |||
}, | |||
{ | |||
"fieldname": "terms", | |||
"fieldtype": "Table", | |||
"label": "Terms", | |||
"options": "Appointment Letter content", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "template_name", | |||
"fieldtype": "Data", | |||
"label": "Template Name", | |||
"reqd": 1, | |||
"unique": 1 | |||
} | |||
], | |||
"links": [], | |||
"modified": "2022-01-18 19:25:14.614616", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Appointment Letter Template", | |||
"naming_rule": "By fieldname", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": 1, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"write": 1 | |||
}, | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": 1, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "HR Manager", | |||
"share": 1, | |||
"write": 1 | |||
} | |||
], | |||
"search_fields": "template_name", | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"states": [], | |||
"title_field": "template_name", | |||
"track_changes": 1 | |||
} |
@@ -0,0 +1,10 @@ | |||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors | |||
# For license information, please see license.txt | |||
# import frappe | |||
from frappe.model.document import Document | |||
class AppointmentLetterTemplate(Document): | |||
pass |
@@ -0,0 +1,9 @@ | |||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
# import frappe | |||
import unittest | |||
class TestAppointmentLetterTemplate(unittest.TestCase): | |||
pass |
@@ -0,0 +1 @@ | |||
Performance of an Employee in a Time Period against given goals. |
@@ -0,0 +1,79 @@ | |||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
// License: GNU General Public License v3. See license.txt | |||
frappe.ui.form.on('Appraisal', { | |||
setup: function(frm) { | |||
frm.add_fetch('employee', 'company', 'company'); | |||
frm.add_fetch('employee', 'employee_name', 'employee_name'); | |||
frm.fields_dict.employee.get_query = function(doc,cdt,cdn) { | |||
return{ query: "erpnext.controllers.queries.employee_query" } | |||
}; | |||
}, | |||
onload: function(frm) { | |||
if(!frm.doc.status) { | |||
frm.set_value('status', 'Draft'); | |||
} | |||
}, | |||
kra_template: function(frm) { | |||
frm.doc.goals = []; | |||
erpnext.utils.map_current_doc({ | |||
method: "hrms.hr.doctype.appraisal.appraisal.fetch_appraisal_template", | |||
source_name: frm.doc.kra_template, | |||
frm: frm | |||
}); | |||
}, | |||
calculate_total: function(frm) { | |||
let goals = frm.doc.goals || []; | |||
let total = 0; | |||
if (goals == []) { | |||
frm.set_value('total_score', 0); | |||
return; | |||
} | |||
for (let i = 0; i<goals.length; i++) { | |||
total = flt(total)+flt(goals[i].score_earned) | |||
} | |||
if (!isNaN(total)) { | |||
frm.set_value('total_score', total); | |||
frm.refresh_field('calculate_total'); | |||
} | |||
}, | |||
set_score_earned: function(frm) { | |||
let goals = frm.doc.goals || []; | |||
for (let i = 0; i<goals.length; i++) { | |||
var d = locals[goals[i].doctype][goals[i].name]; | |||
if (d.score && d.per_weightage) { | |||
d.score_earned = flt(d.per_weightage*d.score, precision("score_earned", d))/100; | |||
} | |||
else { | |||
d.score_earned = 0; | |||
} | |||
refresh_field('score_earned', d.name, 'goals'); | |||
} | |||
frm.trigger('calculate_total'); | |||
} | |||
}); | |||
frappe.ui.form.on('Appraisal Goal', { | |||
score: function(frm, cdt, cdn) { | |||
var d = locals[cdt][cdn]; | |||
if (flt(d.score) > 5) { | |||
frappe.msgprint(__("Score must be less than or equal to 5")); | |||
d.score = 0; | |||
refresh_field('score', d.name, 'goals'); | |||
} | |||
else { | |||
frm.trigger('set_score_earned'); | |||
} | |||
}, | |||
per_weightage: function(frm) { | |||
frm.trigger('set_score_earned'); | |||
}, | |||
goals_remove: function(frm) { | |||
frm.trigger('set_score_earned'); | |||
} | |||
}); |
@@ -0,0 +1,254 @@ | |||
{ | |||
"actions": [], | |||
"autoname": "naming_series:", | |||
"creation": "2013-01-10 16:34:12", | |||
"doctype": "DocType", | |||
"document_type": "Setup", | |||
"engine": "InnoDB", | |||
"field_order": [ | |||
"employee_details", | |||
"naming_series", | |||
"kra_template", | |||
"employee", | |||
"employee_name", | |||
"column_break0", | |||
"status", | |||
"start_date", | |||
"end_date", | |||
"department", | |||
"section_break0", | |||
"goals", | |||
"total_score", | |||
"section_break1", | |||
"remarks", | |||
"other_details", | |||
"company", | |||
"column_break_17", | |||
"amended_from" | |||
], | |||
"fields": [ | |||
{ | |||
"fieldname": "employee_details", | |||
"fieldtype": "Section Break", | |||
"oldfieldtype": "Section Break" | |||
}, | |||
{ | |||
"fieldname": "naming_series", | |||
"fieldtype": "Select", | |||
"label": "Series", | |||
"no_copy": 1, | |||
"options": "HR-APR-.YY.-.MM.", | |||
"print_hide": 1, | |||
"reqd": 1, | |||
"set_only_once": 1 | |||
}, | |||
{ | |||
"fieldname": "kra_template", | |||
"fieldtype": "Link", | |||
"in_standard_filter": 1, | |||
"label": "Appraisal Template", | |||
"oldfieldname": "kra_template", | |||
"oldfieldtype": "Link", | |||
"options": "Appraisal Template", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"depends_on": "kra_template", | |||
"fieldname": "employee", | |||
"fieldtype": "Link", | |||
"in_global_search": 1, | |||
"in_standard_filter": 1, | |||
"label": "For Employee", | |||
"oldfieldname": "employee", | |||
"oldfieldtype": "Link", | |||
"options": "Employee", | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"depends_on": "kra_template", | |||
"fieldname": "employee_name", | |||
"fieldtype": "Data", | |||
"in_global_search": 1, | |||
"label": "For Employee Name", | |||
"oldfieldname": "employee_name", | |||
"oldfieldtype": "Data", | |||
"read_only": 1 | |||
}, | |||
{ | |||
"depends_on": "kra_template", | |||
"fieldname": "column_break0", | |||
"fieldtype": "Column Break", | |||
"oldfieldtype": "Column Break", | |||
"width": "50%" | |||
}, | |||
{ | |||
"default": "Draft", | |||
"depends_on": "kra_template", | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"in_standard_filter": 1, | |||
"label": "Status", | |||
"no_copy": 1, | |||
"oldfieldname": "status", | |||
"oldfieldtype": "Select", | |||
"options": "\nDraft\nSubmitted\nCompleted\nCancelled", | |||
"read_only": 1, | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"depends_on": "kra_template", | |||
"fieldname": "start_date", | |||
"fieldtype": "Date", | |||
"in_list_view": 1, | |||
"label": "Start Date", | |||
"oldfieldname": "start_date", | |||
"oldfieldtype": "Date", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"depends_on": "kra_template", | |||
"fieldname": "end_date", | |||
"fieldtype": "Date", | |||
"label": "End Date", | |||
"oldfieldname": "end_date", | |||
"oldfieldtype": "Date", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fetch_from": "employee.department", | |||
"fieldname": "department", | |||
"fieldtype": "Link", | |||
"label": "Department", | |||
"options": "Department", | |||
"read_only": 1 | |||
}, | |||
{ | |||
"depends_on": "kra_template", | |||
"fieldname": "section_break0", | |||
"fieldtype": "Section Break", | |||
"label": "Goals", | |||
"oldfieldtype": "Section Break", | |||
"options": "Simple" | |||
}, | |||
{ | |||
"fieldname": "goals", | |||
"fieldtype": "Table", | |||
"label": "Goals", | |||
"oldfieldname": "appraisal_details", | |||
"oldfieldtype": "Table", | |||
"options": "Appraisal Goal" | |||
}, | |||
{ | |||
"fieldname": "total_score", | |||
"fieldtype": "Float", | |||
"in_list_view": 1, | |||
"label": "Total Score (Out of 5)", | |||
"no_copy": 1, | |||
"oldfieldname": "total_score", | |||
"oldfieldtype": "Currency", | |||
"read_only": 1 | |||
}, | |||
{ | |||
"depends_on": "kra_template", | |||
"fieldname": "section_break1", | |||
"fieldtype": "Section Break" | |||
}, | |||
{ | |||
"description": "Any other remarks, noteworthy effort that should go in the records.", | |||
"fieldname": "remarks", | |||
"fieldtype": "Text", | |||
"label": "Remarks" | |||
}, | |||
{ | |||
"depends_on": "kra_template", | |||
"fieldname": "other_details", | |||
"fieldtype": "Section Break" | |||
}, | |||
{ | |||
"fieldname": "company", | |||
"fieldtype": "Link", | |||
"label": "Company", | |||
"oldfieldname": "company", | |||
"oldfieldtype": "Link", | |||
"options": "Company", | |||
"remember_last_selected_value": 1, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "column_break_17", | |||
"fieldtype": "Column Break" | |||
}, | |||
{ | |||
"fieldname": "amended_from", | |||
"fieldtype": "Link", | |||
"hidden": 1, | |||
"ignore_user_permissions": 1, | |||
"label": "Amended From", | |||
"no_copy": 1, | |||
"oldfieldname": "amended_from", | |||
"oldfieldtype": "Data", | |||
"options": "Appraisal", | |||
"print_hide": 1, | |||
"read_only": 1, | |||
"report_hide": 1, | |||
"width": "150px" | |||
} | |||
], | |||
"icon": "fa fa-thumbs-up", | |||
"idx": 1, | |||
"index_web_pages_for_search": 1, | |||
"is_submittable": 1, | |||
"links": [], | |||
"modified": "2020-10-03 21:48:33.297065", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Appraisal", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"email": 1, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "Employee", | |||
"share": 1, | |||
"write": 1 | |||
}, | |||
{ | |||
"amend": 1, | |||
"cancel": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"submit": 1, | |||
"write": 1 | |||
}, | |||
{ | |||
"amend": 1, | |||
"cancel": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "HR User", | |||
"share": 1, | |||
"submit": 1, | |||
"write": 1 | |||
} | |||
], | |||
"search_fields": "status, employee, employee_name", | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"timeline_field": "employee", | |||
"title_field": "employee_name" | |||
} |
@@ -0,0 +1,94 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# License: GNU General Public License v3. See license.txt | |||
import frappe | |||
from frappe import _ | |||
from frappe.model.document import Document | |||
from frappe.model.mapper import get_mapped_doc | |||
from frappe.utils import flt, getdate | |||
from hrms.hr.utils import set_employee_name, validate_active_employee | |||
class Appraisal(Document): | |||
def validate(self): | |||
if not self.status: | |||
self.status = "Draft" | |||
if not self.goals: | |||
frappe.throw(_("Goals cannot be empty")) | |||
validate_active_employee(self.employee) | |||
set_employee_name(self) | |||
self.validate_dates() | |||
self.validate_existing_appraisal() | |||
self.calculate_total() | |||
def get_employee_name(self): | |||
self.employee_name = frappe.db.get_value("Employee", self.employee, "employee_name") | |||
return self.employee_name | |||
def validate_dates(self): | |||
if getdate(self.start_date) > getdate(self.end_date): | |||
frappe.throw(_("End Date can not be less than Start Date")) | |||
def validate_existing_appraisal(self): | |||
chk = frappe.db.sql( | |||
"""select name from `tabAppraisal` where employee=%s | |||
and (status='Submitted' or status='Completed') | |||
and ((start_date>=%s and start_date<=%s) | |||
or (end_date>=%s and end_date<=%s))""", | |||
(self.employee, self.start_date, self.end_date, self.start_date, self.end_date), | |||
) | |||
if chk: | |||
frappe.throw( | |||
_("Appraisal {0} created for Employee {1} in the given date range").format( | |||
chk[0][0], self.employee_name | |||
) | |||
) | |||
def calculate_total(self): | |||
total, total_w = 0, 0 | |||
for d in self.get("goals"): | |||
if d.score: | |||
d.score_earned = flt(d.score) * flt(d.per_weightage) / 100 | |||
total = total + d.score_earned | |||
total_w += flt(d.per_weightage) | |||
if int(total_w) != 100: | |||
frappe.throw( | |||
_("Total weightage assigned should be 100%.<br>It is {0}").format(str(total_w) + "%") | |||
) | |||
if ( | |||
frappe.db.get_value("Employee", self.employee, "user_id") != frappe.session.user and total == 0 | |||
): | |||
frappe.throw(_("Total cannot be zero")) | |||
self.total_score = total | |||
def on_submit(self): | |||
frappe.db.set(self, "status", "Submitted") | |||
def on_cancel(self): | |||
frappe.db.set(self, "status", "Cancelled") | |||
@frappe.whitelist() | |||
def fetch_appraisal_template(source_name, target_doc=None): | |||
target_doc = get_mapped_doc( | |||
"Appraisal Template", | |||
source_name, | |||
{ | |||
"Appraisal Template": { | |||
"doctype": "Appraisal", | |||
}, | |||
"Appraisal Template Goal": { | |||
"doctype": "Appraisal Goal", | |||
}, | |||
}, | |||
target_doc, | |||
) | |||
return target_doc |
@@ -0,0 +1,10 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors | |||
# See license.txt | |||
import unittest | |||
# test_records = frappe.get_test_records('Appraisal') | |||
class TestAppraisal(unittest.TestCase): | |||
pass |
@@ -0,0 +1 @@ | |||
Goal for the parent Appraisal. |
@@ -0,0 +1,220 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_import": 0, | |||
"allow_rename": 0, | |||
"autoname": "hash", | |||
"beta": 0, | |||
"creation": "2013-02-22 01:27:44", | |||
"custom": 0, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"editable_grid": 1, | |||
"fields": [ | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"description": "Key Responsibility Area", | |||
"fieldname": "kra", | |||
"fieldtype": "Small Text", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Goal", | |||
"length": 0, | |||
"no_copy": 0, | |||
"oldfieldname": "kra", | |||
"oldfieldtype": "Small Text", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "240px", | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0, | |||
"width": "240px" | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "section_break_2", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "per_weightage", | |||
"fieldtype": "Float", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Weightage (%)", | |||
"length": 0, | |||
"no_copy": 0, | |||
"oldfieldname": "per_weightage", | |||
"oldfieldtype": "Currency", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "70px", | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0, | |||
"width": "70px" | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "column_break_4", | |||
"fieldtype": "Column Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "score", | |||
"fieldtype": "Float", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Score (0-5)", | |||
"length": 0, | |||
"no_copy": 1, | |||
"oldfieldname": "score", | |||
"oldfieldtype": "Select", | |||
"options": "", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "70px", | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0, | |||
"width": "70px" | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "section_break_6", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "score_earned", | |||
"fieldtype": "Float", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Score Earned", | |||
"length": 0, | |||
"no_copy": 1, | |||
"oldfieldname": "score_earned", | |||
"oldfieldtype": "Currency", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "70px", | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0, | |||
"width": "70px" | |||
} | |||
], | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"idx": 1, | |||
"image_view": 0, | |||
"in_create": 0, | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 1, | |||
"max_attachments": 0, | |||
"modified": "2020-09-18 17:26:09.703215", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Appraisal Goal", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"quick_entry": 0, | |||
"read_only": 0, | |||
"read_only_onload": 0, | |||
"track_seen": 0 | |||
} |
@@ -0,0 +1,9 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# License: GNU General Public License v3. See license.txt | |||
from frappe.model.document import Document | |||
class AppraisalGoal(Document): | |||
pass |
@@ -0,0 +1 @@ | |||
Standard set of goals for an Employee / Designation / Job Profile. New Appraisal transactions can be created from the Template. |
@@ -0,0 +1,8 @@ | |||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors | |||
// For license information, please see license.txt | |||
frappe.ui.form.on('Appraisal Template', { | |||
refresh: function(frm) { | |||
} | |||
}); |
@@ -0,0 +1,170 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_import": 1, | |||
"allow_rename": 1, | |||
"autoname": "field:kra_title", | |||
"beta": 0, | |||
"creation": "2012-07-03 13:30:39", | |||
"custom": 0, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Setup", | |||
"editable_grid": 0, | |||
"fields": [ | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "kra_title", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 0, | |||
"label": "Appraisal Template Title", | |||
"length": 0, | |||
"no_copy": 0, | |||
"oldfieldname": "kra_title", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "description", | |||
"fieldtype": "Small Text", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 0, | |||
"label": "Description", | |||
"length": 0, | |||
"no_copy": 0, | |||
"oldfieldname": "description", | |||
"oldfieldtype": "Small Text", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "300px", | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0, | |||
"width": "300px" | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "goals", | |||
"fieldtype": "Table", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Goals", | |||
"length": 0, | |||
"no_copy": 0, | |||
"oldfieldname": "kra_sheet", | |||
"oldfieldtype": "Table", | |||
"options": "Appraisal Template Goal", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"icon": "icon-file-text", | |||
"idx": 1, | |||
"image_view": 0, | |||
"in_create": 0, | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2020-09-18 17:26:09.703215", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Appraisal Template", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 0, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 0, | |||
"email": 1, | |||
"export": 0, | |||
"if_owner": 0, | |||
"import": 0, | |||
"is_custom": 0, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "HR User", | |||
"set_user_permissions": 0, | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 0, | |||
"cancel": 0, | |||
"create": 0, | |||
"delete": 0, | |||
"email": 0, | |||
"export": 0, | |||
"if_owner": 0, | |||
"import": 0, | |||
"is_custom": 0, | |||
"permlevel": 0, | |||
"print": 0, | |||
"read": 1, | |||
"report": 0, | |||
"role": "Employee", | |||
"set_user_permissions": 0, | |||
"share": 0, | |||
"submit": 0, | |||
"write": 0 | |||
} | |||
], | |||
"quick_entry": 0, | |||
"read_only": 0, | |||
"read_only_onload": 0, | |||
"sort_order": "DESC", | |||
"track_seen": 0 | |||
} |
@@ -0,0 +1,21 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# License: GNU General Public License v3. See license.txt | |||
import frappe | |||
from frappe import _ | |||
from frappe.model.document import Document | |||
from frappe.utils import cint, flt | |||
class AppraisalTemplate(Document): | |||
def validate(self): | |||
self.check_total_points() | |||
def check_total_points(self): | |||
total_points = 0 | |||
for d in self.get("goals"): | |||
total_points += flt(d.per_weightage) | |||
if cint(total_points) != 100: | |||
frappe.throw(_("Sum of points for all goals should be 100. It is {0}").format(total_points)) |
@@ -0,0 +1,7 @@ | |||
def get_data(): | |||
return { | |||
"fieldname": "kra_template", | |||
"transactions": [ | |||
{"items": ["Appraisal"]}, | |||
], | |||
} |
@@ -0,0 +1,10 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors | |||
# See license.txt | |||
import unittest | |||
# test_records = frappe.get_test_records('Appraisal Template') | |||
class TestAppraisalTemplate(unittest.TestCase): | |||
pass |
@@ -0,0 +1 @@ | |||
Goal details for the parent Appraisal Template. |
@@ -0,0 +1,91 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_import": 0, | |||
"allow_rename": 0, | |||
"autoname": "hash", | |||
"beta": 0, | |||
"creation": "2013-02-22 01:27:44", | |||
"custom": 0, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"editable_grid": 1, | |||
"fields": [ | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"description": "Key Performance Area", | |||
"fieldname": "kra", | |||
"fieldtype": "Small Text", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "KRA", | |||
"length": 0, | |||
"no_copy": 0, | |||
"oldfieldname": "kra", | |||
"oldfieldtype": "Small Text", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "200px", | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0, | |||
"width": "200px" | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "per_weightage", | |||
"fieldtype": "Float", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Weightage (%)", | |||
"length": 0, | |||
"no_copy": 0, | |||
"oldfieldname": "per_weightage", | |||
"oldfieldtype": "Currency", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "100px", | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0, | |||
"width": "100px" | |||
} | |||
], | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"idx": 1, | |||
"image_view": 0, | |||
"in_create": 0, | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 1, | |||
"max_attachments": 0, | |||
"modified": "2020-09-18 17:26:09.703215", | |||
"modified_by": "Administrator", | |||
"module": "HR", | |||
"name": "Appraisal Template Goal", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"quick_entry": 0, | |||
"read_only": 0, | |||
"read_only_onload": 0, | |||
"track_seen": 0 | |||
} |