@@ -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 | |||||
} |