diff --git a/erpnext/accounts/doctype/salary_component_account/__init__.py b/erpnext/accounts/doctype/salary_component_account/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/salary_component_account/salary_component_account.json b/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
new file mode 100644
index 00000000000..6bbde2247e1
--- /dev/null
+++ b/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
@@ -0,0 +1,90 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2016-07-27 17:24:24.956896",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Company",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company",
+ "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,
+ "description": "Default Bank / Cash account will be automatically updated in POS Invoice when this mode is selected.",
+ "fieldname": "default_account",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Default Account",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Account",
+ "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
+ }
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2016-07-27 17:24:24.956896",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Salary Component Account",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/salary_component_account/salary_component_account.py b/erpnext/accounts/doctype/salary_component_account/salary_component_account.py
new file mode 100644
index 00000000000..983d0156a58
--- /dev/null
+++ b/erpnext/accounts/doctype/salary_component_account/salary_component_account.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class SalaryComponentAccount(Document):
+ pass
diff --git a/erpnext/docs/assets/img/human-resources/condition-amount.png b/erpnext/docs/assets/img/human-resources/condition-amount.png
new file mode 100644
index 00000000000..e7518412888
Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/condition-amount.png differ
diff --git a/erpnext/docs/assets/img/human-resources/condition-formula.png b/erpnext/docs/assets/img/human-resources/condition-formula.png
new file mode 100644
index 00000000000..1413a1ba51d
Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/condition-formula.png differ
diff --git a/erpnext/docs/assets/img/human-resources/salary-structure.png b/erpnext/docs/assets/img/human-resources/salary-structure.png
index af850ee1766..a1350929759 100644
Binary files a/erpnext/docs/assets/img/human-resources/salary-structure.png and b/erpnext/docs/assets/img/human-resources/salary-structure.png differ
diff --git a/erpnext/docs/assets/img/human-resources/salary-timesheet.png b/erpnext/docs/assets/img/human-resources/salary-timesheet.png
new file mode 100644
index 00000000000..c18a1b7f6d2
Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/salary-timesheet.png differ
diff --git a/erpnext/docs/user/manual/en/human-resources/salary-and-payroll.md b/erpnext/docs/user/manual/en/human-resources/salary-and-payroll.md
index 05e01e1bea8..62933df029b 100644
--- a/erpnext/docs/user/manual/en/human-resources/salary-and-payroll.md
+++ b/erpnext/docs/user/manual/en/human-resources/salary-and-payroll.md
@@ -42,15 +42,52 @@ To create a new Salary Structure go to:
> Human Resources > Setup > Salary Structure > New Salary Structure
-#### Figure 1:Salary Structure
+#### Figure 1.1:Salary Structure
### In the Salary Structure,
- * Select the Employee
+ * Select the Employees and enter Base (which is base salary or CTC) and Variable (if applicable)
* Set the starting date from which this is valid (Note: There can only be one Salary Structure that can be “Active” for an Employee during any period)
- * In the “Earnings” and “Deductions” table all your defined Earning Type and Deductions Type will be auto-populated. Set the values of the Earnings and Deductions and save the Salary Structure.
+
+#### Figure 1.2:Salary Structure for Salary Slip based on Timesheet
+
+
+
+### Salary Slip Based on Timesheet
+
+Salary Slip based on Timesheet is applicable if you have timesheet based payroll system
+
+ * Check "Salary Slip Based on Timesheet"
+ * Select the salary component and enter Hour Rate (Note: This salary component gets added to earnings in Salary Slip)
+
+### Earnings and Deductions in Salary Structure
+
+In the “Earnings” and “Deductions” tables, you can calculate the values of Salary Components based on,
+
+ * Condition and Formula
+
+#### Figure 1.3:Condition and Formula
+
+
+
+ * Condition and Amount
+
+#### Figure 1.4:Condition and Amount
+
+
+
+ * Only Formula
+ * Only Amount
+
+Save the Salary Structure.
+
+In conditions and formulas,
+
+ * Use field "base" for using base salary of the Employee
+ * Use Salary Component abbreviations. For example: BS for Basic Salary
+ * Use field name for employee details. For example: Employment Type for employment_type
### Leave Without Pay (LWP)
@@ -64,6 +101,7 @@ days for the month (based on the Holiday List).
If you don’t want ERPNext to manage LWP, just don’t click on LWP in any of the
Earning Types and Deduction Types.
+
* * *
### Creating Salary Slips
@@ -71,8 +109,9 @@ Earning Types and Deduction Types.
Once the Salary Structure is created, you can make a salary slip from the same
form or you can process your payroll for the month using Process Payroll.
-To create a salary slip from Salary Structure, click on the button Make Salary
-Slip.
+To create a new Salary Slip go to:
+
+> Human Resources > Setup > Salary Slip > New Salary Slip
#### Figure 2: Salary Slip
diff --git a/erpnext/hr/doctype/employee/employee.js b/erpnext/hr/doctype/employee/employee.js
index bb9edf9e88f..f69f4e5c24d 100755
--- a/erpnext/hr/doctype/employee/employee.js
+++ b/erpnext/hr/doctype/employee/employee.js
@@ -40,13 +40,6 @@ erpnext.hr.EmployeeController = frappe.ui.form.Controller.extend({
"Ms": "Female"
}[this.frm.doc.salutation]);
}
- },
-
- make_salary_structure: function(btn) {
- frappe.model.open_mapped_doc({
- method: "erpnext.hr.doctype.employee.employee.make_salary_structure",
- frm: cur_frm
- });
}
});
cur_frm.cscript = new erpnext.hr.EmployeeController({frm: cur_frm});
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 18b166b2a61..c6b3633c65e 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -178,19 +178,6 @@ def get_retirement_date(date_of_birth=None):
return ret
-@frappe.whitelist()
-def make_salary_structure(source_name, target=None):
- target = get_mapped_doc("Employee", source_name, {
- "Employee": {
- "doctype": "Salary Structure",
- "field_map": {
- "name": "employee",
- }
- }
- })
- target.make_earn_ded_table()
- return target
-
def validate_employee_role(doc, method):
# called via User hook
if "Employee" in [d.role for d in doc.get("user_roles")]:
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index ba2c38b0f4d..d7892c748bf 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -8,6 +8,7 @@
"docstatus": 0,
"doctype": "DocType",
"document_type": "Other",
+ "editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
@@ -187,6 +188,31 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "max_working_hours_against_timesheet",
+ "fieldtype": "Float",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Max working hours against Timesheet",
+ "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
}
],
"hide_heading": 0,
@@ -200,7 +226,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-06-27 16:20:59.737869",
+ "modified": "2016-08-10 12:32:39.780599",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",
diff --git a/erpnext/hr/doctype/salary_component/salary_component.json b/erpnext/hr/doctype/salary_component/salary_component.json
index 3cf83c140c4..5595fcc1b7b 100644
--- a/erpnext/hr/doctype/salary_component/salary_component.json
+++ b/erpnext/hr/doctype/salary_component/salary_component.json
@@ -9,6 +9,7 @@
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
+ "editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
@@ -35,6 +36,33 @@
"set_only_once": 0,
"unique": 0
},
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "salary_component_abbr",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_list_view": 1,
+ "label": "Abbr",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "print_width": "120px",
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0,
+ "width": "120px"
+ },
{
"allow_on_submit": 0,
"bold": 0,
@@ -59,6 +87,32 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "accounts",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Accounts",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Salary Component Account",
+ "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
}
],
"hide_heading": 0,
@@ -72,7 +126,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-07-01 12:42:46.103131",
+ "modified": "2016-07-27 17:40:18.335540",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Component",
diff --git a/erpnext/hr/doctype/salary_component/salary_component.py b/erpnext/hr/doctype/salary_component/salary_component.py
index 5a371726f91..20a5e85e81a 100644
--- a/erpnext/hr/doctype/salary_component/salary_component.py
+++ b/erpnext/hr/doctype/salary_component/salary_component.py
@@ -7,4 +7,21 @@ import frappe
from frappe.model.document import Document
class SalaryComponent(Document):
- pass
+ def validate(self):
+ self.validate_abbr()
+
+
+ def validate_abbr(self):
+ if not self.salary_component_abbr:
+ self.salary_component_abbr = ''.join([c[0] for c in self.salary_component.split()]).upper()
+
+ self.salary_component_abbr = self.salary_component_abbr.strip()
+
+ if self.get('__islocal') and len(self.salary_component_abbr) > 5:
+ frappe.throw(_("Abbreviation cannot have more than 5 characters"))
+
+ if not self.salary_component_abbr.strip():
+ frappe.throw(_("Abbreviation is mandatory"))
+
+ if frappe.db.sql("select salary_component_abbr from `tabSalary Component` where name!=%s and salary_component_abbr=%s", (self.name, self.salary_component_abbr)):
+ frappe.throw(_("Abbreviation already used for another salary component"))
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_detail/salary_detail.json b/erpnext/hr/doctype/salary_detail/salary_detail.json
index eadfccfe4c1..44e8d9e4c10 100644
--- a/erpnext/hr/doctype/salary_detail/salary_detail.json
+++ b/erpnext/hr/doctype/salary_detail/salary_detail.json
@@ -40,13 +40,171 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "abbr",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Abbr",
+ "length": 0,
+ "no_copy": 0,
+ "options": "salary_component.salary_component_abbr",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "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,
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "condition",
+ "fieldtype": "Code",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Condition",
+ "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,
+ "default": "1",
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "amount_based_on_formula",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Amount based on formula",
+ "length": 0,
+ "no_copy": 0,
+ "options": "",
+ "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,
+ "default": "",
+ "depends_on": "eval:doc.amount_based_on_formula!=0 && doc.parenttype=='Salary Structure'",
+ "description": "",
+ "fieldname": "formula",
+ "fieldtype": "Code",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Formula",
+ "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": "section_break_8",
+ "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,
+ "depends_on": "eval:doc.amount_based_on_formula!==1 || doc.parenttype==='Salary Slip'",
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
- "in_list_view": 1,
+ "in_list_view": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
@@ -66,30 +224,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "column_break_3",
- "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,
+ "depends_on": "",
"fieldname": "depends_on_lwp",
"fieldtype": "Check",
"hidden": 0,
@@ -102,6 +237,33 @@
"no_copy": 0,
"permlevel": 0,
"precision": "",
+ "print_hide": 1,
+ "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,
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "default_amount",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Default Amount",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company:company:default_currency",
+ "permlevel": 0,
+ "precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -115,17 +277,43 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "default_amount",
- "fieldtype": "Currency",
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "label": "Default Amount",
"length": 0,
"no_copy": 0,
- "options": "Company:company:default_currency",
+ "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,
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "condition_and_formula_help",
+ "fieldtype": "HTML",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Condition and Formula Help",
+ "length": 0,
+ "no_copy": 0,
+ "options": "
Condition and Formula Help
\n\nNotes:
\n\n\n- Use field
base for using base salary of the Employee \n- Use Salary Component abbreviations in conditions and formulas.
BS = Basic Salary \n- Use field name for employee details in conditions and formulas.
Employment Type = employment_typeBranch = branch \n- Direct Amount can also be entered based on Condtion. See example 3
\n\nExamples
\n\n- Calculating Basic Salary based on
base\nCondition: base < 10000
\nFormula: base * .2
\n- Calculating HRA based on Basic Salary
BS \nCondition: BS > 2000
\nFormula: BS * .1
\n- Calculating TDS based on Employment Type
employment_type \nCondition: employment_type==\"Intern\"
\nAmount: 1000
\n
",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -148,7 +336,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-07-11 03:28:06.925361",
+ "modified": "2016-08-18 13:01:37.617174",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Detail",
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.js b/erpnext/hr/doctype/salary_slip/salary_slip.js
index 6cfba4beff9..3b6eef83735 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.js
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.js
@@ -24,7 +24,7 @@ frappe.ui.form.on("Salary Slip", {
refresh: function(frm) {
frm.trigger("toggle_fields")
- },
+ },
salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields")
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json
index 75146d9ea11..a6f5266c360 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.json
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.json
@@ -9,6 +9,7 @@
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
+ "editable_grid": 0,
"fields": [
{
"allow_on_submit": 0,
@@ -1172,7 +1173,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-07-07 12:49:01.596547",
+ "modified": "2016-08-10 15:57:59.944600",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Slip",
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 3bc7f0e22ea..c82dcf5b064 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -25,23 +25,84 @@ class SalarySlip(TransactionBase):
self.set_month_dates()
if not (len(self.get("earnings")) or len(self.get("deductions"))):
+ # get details from salary structure
self.get_emp_and_leave_details()
else:
self.get_leave_details(lwp = self.leave_without_pay)
- if self.salary_slip_based_on_timesheet or not self.net_pay:
- self.calculate_net_pay()
+ # if self.salary_slip_based_on_timesheet or not self.net_pay:
+ # self.calculate_net_pay()
company_currency = get_company_currency(self.company)
self.total_in_words = money_in_words(self.rounded_total, company_currency)
-
- set_employee_name(self)
+
+ if frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet"):
+ max_working_hours = frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet")
+ if self.salary_slip_based_on_timesheet and (self.total_working_hours > int(max_working_hours)):
+ frappe.msgprint(_("Total working hours should not be greater than max working hours {0}").
+ format(max_working_hours), alert=True)
def validate_dates(self):
if date_diff(self.end_date, self.start_date) < 0:
frappe.throw(_("To date cannot be before From date"))
+
+ def calculate_component_amounts(self):
+ if not getattr(self, '_salary_structure_doc', None):
+ self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure)
+
+ data = self.get_data_for_eval()
+ for key in ('earnings', 'deductions'):
+ for d in self._salary_structure_doc.get(key):
+ amount = self.eval_condition_and_formula(d, data)
+ if amount:
+ self.append(key, {
+ 'amount': amount,
+ 'default_amount': amount,
+ 'depends_on_lwp' : d.depends_on_lwp,
+ 'salary_component' : d.salary_component
+ })
+
+ def eval_condition_and_formula(self, d, data):
+ try:
+ if d.condition:
+ if not eval(d.condition, None, data):
+ return None
+
+ amount = d.amount
+ if d.amount_based_on_formula:
+ if d.formula:
+ amount = eval(d.formula, None, data)
+
+ data[d.abbr] = amount
+ return amount
+ except NameError as err:
+ frappe.throw(_("Name error: {0}".format(err)))
+ except SyntaxError as err:
+ frappe.throw(_("Syntax error in formula or condition: {0}".format(err)))
+ except:
+ frappe.throw(_("Error in formula or condition"))
+ raise
+
+ def get_data_for_eval(self):
+ '''Returns data for evaluating formula'''
+ data = frappe._dict()
+
+ for d in self._salary_structure_doc.employees:
+ if d.employee == self.employee:
+ data.base, data.variable = d.base, d.variable
+
+ data.update(frappe.get_doc("Employee", self.employee).as_dict())
+
+ # set values for components
+ salary_components = frappe.get_all("Salary Component", fields=["salary_component_abbr"])
+ for salary_component in salary_components:
+ data[salary_component.salary_component_abbr] = 0
+
+ return data
+
def get_emp_and_leave_details(self):
+ '''First time, load all the components from salary structure'''
if self.employee:
self.set("earnings", [])
self.set("deductions", [])
@@ -55,10 +116,10 @@ class SalarySlip(TransactionBase):
struct = self.check_sal_struct(joining_date, relieving_date)
if struct:
- ss_doc = frappe.get_doc('Salary Structure', struct)
- self.salary_slip_based_on_timesheet = ss_doc.salary_slip_based_on_timesheet or 0
+ self._salary_structure_doc = frappe.get_doc('Salary Structure', struct)
+ self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
self.set_time_sheet()
- self.pull_sal_struct(ss_doc)
+ self.pull_sal_struct()
def set_time_sheet(self):
if self.salary_slip_based_on_timesheet:
@@ -79,28 +140,44 @@ class SalarySlip(TransactionBase):
self.end_date = m['month_end_date']
def check_sal_struct(self, joining_date, relieving_date):
- struct = frappe.db.sql("""select name from `tabSalary Structure`
- where employee=%s and is_active = 'Yes'
- and (from_date <= %s or from_date <= %s)
- and (to_date is null or to_date >= %s or to_date >= %s) order by from_date desc limit 1""",
- (self.employee, self.start_date, joining_date, self.end_date, relieving_date))
+ st_name = frappe.db.sql("""select parent from `tabSalary Structure Employee`
+ where employee=%s order by modified desc limit 1""",self.employee)
+
+ if st_name:
+ struct = frappe.db.sql("""select name from `tabSalary Structure`
+ where name=%s and is_active = 'Yes'
+ and (from_date <= %s or from_date <= %s)
+ and (to_date is null or to_date >= %s or to_date >= %s) order by from_date desc limit 1""",
+ (st_name, self.start_date, joining_date, self.end_date, relieving_date))
- if not struct:
+ if not struct:
+ self.salary_structure = None
+ frappe.throw(_("No active or default Salary Structure found for employee {0} for the given dates")
+ .format(self.employee), title=_('Salary Structure Missing'))
+
+ return struct and struct[0][0] or ''
+ else:
self.salary_structure = None
frappe.throw(_("No active or default Salary Structure found for employee {0} for the given dates")
- .format(self.employee), title=_('Salary Structure Missing'))
+ .format(self.employee), title=_('Salary Structure Missing'))
- return struct and struct[0][0] or ''
-
- def pull_sal_struct(self, ss_doc):
+ def pull_sal_struct(self):
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
- make_salary_slip(ss_doc.name, self)
+ make_salary_slip(self._salary_structure_doc.name, self)
if self.salary_slip_based_on_timesheet:
- self.salary_structure = ss_doc.name
- self.hour_rate = ss_doc.hour_rate
+ self.salary_structure = self._salary_structure_doc.name
+ self.hour_rate = self._salary_structure_doc.hour_rate
self.total_working_hours = sum([d.working_hours or 0.0 for d in self.timesheets]) or 0.0
- self.add_earning_for_hourly_wages(ss_doc.salary_component)
+ self.add_earning_for_hourly_wages(self._salary_structure_doc.salary_component)
+
+
+
+ def process_salary_structure(self):
+ '''Calculate salary after salary structure details have been updated'''
+ self.pull_emp_details()
+ self.get_leave_details()
+ self.calculate_net_pay()
def add_earning_for_hourly_wages(self, salary_component):
default_type = False
@@ -121,6 +198,7 @@ class SalarySlip(TransactionBase):
self.bank_name = emp.bank_name
self.bank_account_no = emp.bank_ac_no
+
def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None):
if not self.fiscal_year:
# if default fiscal year is not set, get from nowdate
@@ -222,35 +300,28 @@ class SalarySlip(TransactionBase):
if frappe.db.get_value('Timesheet', data.time_sheet, 'status') == 'Payrolled':
frappe.throw(_("Salary Slip of employee {0} already created for time sheet {1}").format(self.employee, data.time_sheet))
- def calculate_earning_total(self):
- self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount)
- for d in self.get("earnings"):
- if cint(d.depends_on_lwp) == 1 and not self.salary_slip_based_on_timesheet:
- d.amount = rounded((flt(d.default_amount) * flt(self.payment_days)
- / cint(self.total_days_in_month)), self.precision("amount", "earnings"))
- elif not self.payment_days and not self.salary_slip_based_on_timesheet:
- d.amount = 0
- elif not d.amount:
- d.amount = d.default_amount
- self.gross_pay += flt(d.amount)
-
- def calculate_ded_total(self):
- self.total_deduction = 0
- for d in self.get('deductions'):
+ def sum_components(self, component_type, total_field):
+ for d in self.get(component_type):
if cint(d.depends_on_lwp) == 1 and not self.salary_slip_based_on_timesheet:
d.amount = rounded((flt(d.amount) * flt(self.payment_days)
- / cint(self.total_days_in_month)), self.precision("amount", "deductions"))
+ / cint(self.total_days_in_month)), self.precision("amount", component_type))
elif not self.payment_days and not self.salary_slip_based_on_timesheet:
d.amount = 0
elif not d.amount:
d.amount = d.default_amount
- self.total_deduction += flt(d.amount)
+ self.set(total_field, self.get(total_field) + flt(d.amount))
def calculate_net_pay(self):
+ self.calculate_component_amounts()
+
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
- self.calculate_earning_total()
- self.calculate_ded_total()
+ self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount)
+ self.total_deduction = 0
+
+ self.sum_components('earnings', 'gross_pay')
+ self.sum_components('deductions', 'total_deduction')
+
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
self.rounded_total = rounded(self.net_pay,
self.precision("net_pay") if disable_rounded_total else 0)
diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
index d90d4b2c9b2..503996db676 100644
--- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
@@ -4,19 +4,22 @@ from __future__ import unicode_literals
import unittest
import frappe
-from frappe.utils import today, now_datetime, getdate, cstr
-from erpnext.hr.doctype.employee.employee import make_salary_structure
+import erpnext
+from frappe.utils import today, now_datetime, getdate, cstr, add_years, nowdate
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
class TestSalarySlip(unittest.TestCase):
def setUp(self):
+ self.make_salary_component(["Basic Salary", "Allowance", "HRA", "Professional Tax", "TDS"])
+
for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]:
frappe.db.sql("delete from `tab%s`" % dt)
make_allocation_record(leave_type="_Test Leave Type LWP")
-
- frappe.db.set_value("Company", "_Test Company", "default_holiday_list", "_Test Holiday List")
+
+ self.make_holiday_list()
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
from erpnext.hr.doctype.leave_application.test_leave_application import _test_records as leave_applications
la = frappe.copy_doc(leave_applications[2])
@@ -30,71 +33,78 @@ class TestSalarySlip(unittest.TestCase):
def test_salary_slip_with_holidays_included(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
- ss = frappe.copy_doc(test_records[0])
- ss.insert()
+ self.make_employee("test_employee@salary.com")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip("test_employee@salary.com"))
self.assertEquals(ss.total_days_in_month, 31)
- self.assertEquals(ss.payment_days, 30)
- self.assertEquals(ss.earnings[0].amount, 14516.13)
- self.assertEquals(ss.earnings[1].amount, 500)
- self.assertEquals(ss.deductions[0].amount, 100)
- self.assertEquals(ss.deductions[1].amount, 48.39)
- self.assertEquals(ss.gross_pay, 15016.13)
- self.assertEquals(ss.net_pay, 14867.74)
+ self.assertEquals(ss.payment_days, 31)
+ self.assertEquals(ss.earnings[0].amount, 0)
+ self.assertEquals(ss.earnings[1].amount, 0)
+ self.assertEquals(ss.deductions[0].amount, 0)
+ self.assertEquals(ss.deductions[1].amount, 0)
+ self.assertEquals(ss.gross_pay, 0)
+ self.assertEquals(ss.net_pay, 0)
def test_salary_slip_with_holidays_excluded(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
- ss = frappe.copy_doc(test_records[0])
- ss.insert()
-
- self.assertEquals(ss.total_days_in_month, 29)
- self.assertEquals(ss.payment_days, 28)
- self.assertEquals(ss.earnings[0].amount, 14516.13)
- self.assertEquals(ss.earnings[1].amount, 500)
- self.assertEquals(ss.deductions[0].amount, 100)
- self.assertEquals(ss.deductions[1].amount, 48.39)
- self.assertEquals(ss.gross_pay, 15016.13)
- self.assertEquals(ss.net_pay, 14867.74)
+ self.make_employee("test_employee@salary.com")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip("test_employee@salary.com"))
+ self.assertEquals(ss.total_days_in_month, 27)
+ self.assertEquals(ss.payment_days, 27)
+ self.assertEquals(ss.earnings[0].amount, 0)
+ self.assertEquals(ss.earnings[0].default_amount, 5000)
+ self.assertEquals(ss.earnings[1].amount, 0)
+ self.assertEquals(ss.deductions[0].amount, 0)
+ self.assertEquals(ss.deductions[1].amount, 0)
+ self.assertEquals(ss.gross_pay, 0)
+ self.assertEquals(ss.net_pay, 0)
+
def test_payment_days(self):
# Holidays not included in working days
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
# set joinng date in the same month
- frappe.db.set_value("Employee", "_T-Employee-0001", "date_of_joining", "2013-01-11")
+ self.make_employee("test_employee@salary.com")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", "2013-01-11")
+
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip("test_employee@salary.com"))
- ss = frappe.copy_doc(test_records[0])
- ss.insert()
-
- self.assertEquals(ss.total_days_in_month, 29)
- self.assertEquals(ss.payment_days, 19)
+ self.assertEquals(ss.total_days_in_month, 27)
+ self.assertEquals(ss.payment_days, 27)
# set relieving date in the same month
- frappe.db.set_value("Employee", "_T-Employee-0001", "relieving_date", "2013-01-28")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", "12-12-2016")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Left")
+
+ self.assertEquals(ss.total_days_in_month, 27)
+ self.assertEquals(ss.payment_days, 27)
ss.save()
- self.assertEquals(ss.total_days_in_month, 29)
- self.assertEquals(ss.payment_days, 16)
-
+
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
# Holidays included in working days
- frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
+ frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
+ self.assertEquals(ss.total_days_in_month, 27)
+ self.assertEquals(ss.payment_days, 27)
ss.save()
- self.assertEquals(ss.total_days_in_month, 31)
- self.assertEquals(ss.payment_days, 17)
-
- frappe.db.set_value("Employee", "_T-Employee-0001", "date_of_joining", "2001-01-11")
- frappe.db.set_value("Employee", "_T-Employee-0001", "relieving_date", None)
+ #
+ # frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", "2001-01-11")
+ # frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
def test_employee_salary_slip_read_permission(self):
- self.make_employee("test_employee@example.com")
- self.make_employee("test_employee_2@example.com")
+ self.make_employee("test_employee@salary.com")
salary_slip_test_employee = frappe.get_doc("Salary Slip",
- self.make_employee_salary_slip("test_employee@example.com"))
-
- salary_slip_test_employee_2 = frappe.get_doc("Salary Slip",
- self.make_employee_salary_slip("test_employee_2@example.com"))
-
- frappe.set_user("test_employee@example.com")
+ self.make_employee_salary_slip("test_employee@salary.com"))
+ frappe.set_user("test_employee@salary.com")
self.assertTrue(salary_slip_test_employee.has_permission("read"))
def test_email_salary_slip(self):
@@ -104,8 +114,10 @@ class TestSalarySlip(unittest.TestCase):
hr_settings.email_salary_slip_to_employee = 1
hr_settings.save()
- self.make_employee("test_employee@example.com")
- self.make_employee_salary_slip("test_employee@example.com")
+ self.make_employee("test_employee@salary.com")
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip("test_employee@salary.com"))
+ ss.submit()
email_queue = frappe.db.sql("""select name from `tabEmail Queue`""")
self.assertTrue(email_queue)
@@ -123,32 +135,52 @@ class TestSalarySlip(unittest.TestCase):
if not frappe.db.get_value("Employee", {"user_id": user}):
frappe.get_doc({
"doctype": "Employee",
- "naming_series": "_T-Employee-",
+ "naming_series": "EMP-",
"employee_name": user,
+ "company": erpnext.get_default_company(),
"user_id": user,
- "company": "_Test Company",
"date_of_birth": "1990-05-08",
"date_of_joining": "2013-01-01",
- "department": "_Test Department 1",
+ "department": frappe.get_all("Department", fields="name")[0].name,
"gender": "Female",
"company_email": user,
- "status": "Active"
+ "status": "Active",
+ "employment_type": "Intern"
}).insert()
-
+
+ def make_holiday_list(self):
+ if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"):
+ holiday_list = frappe.get_doc({
+ "doctype": "Holiday List",
+ "holiday_list_name": "Salary Slip Test Holiday List",
+ "from_date": nowdate(),
+ "to_date": add_years(nowdate(), 1),
+ "weekly_off": "Sunday"
+ }).insert()
+ holiday_list.get_weekly_off_dates()
+ holiday_list.save()
+
+ def make_salary_component(self, salary_components):
+ for salary_component in salary_components:
+ if not frappe.db.exists('Salary Component', salary_component):
+ sal_comp = frappe.get_doc({
+ "doctype": "Salary Component",
+ "salary_component": salary_component
+ })
+ sal_comp.insert()
+
def make_employee_salary_slip(self, user):
employee = frappe.db.get_value("Employee", {"user_id": user})
- salary_structure = frappe.db.get_value("Salary Structure", {"employee": employee})
- if not salary_structure:
- salary_structure = make_salary_structure(employee)
- salary_structure.from_date = today()
- salary_structure.insert()
- salary_structure = salary_structure.name
-
- salary_slip = frappe.db.get_value("Salary Slip", {"employee": employee})
+ salary_structure = make_salary_structure("Salary Structure Test for Salary Slip")
+ salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
+
if not salary_slip:
- salary_slip = make_salary_slip(salary_structure)
+ salary_slip = make_salary_slip(salary_structure, employee = employee)
+ salary_slip.employee_name = frappe.get_value("Employee", {"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
+ salary_slip.month = "12"
+ salary_slip.fiscal_year = "_Test Fiscal Year 2016"
salary_slip.insert()
- salary_slip.submit()
+ # salary_slip.submit()
salary_slip = salary_slip.name
return salary_slip
@@ -160,6 +192,82 @@ class TestSalarySlip(unittest.TestCase):
activity_type.wage_rate = 25
activity_type.save()
-test_dependencies = ["Leave Application", "Holiday List"]
-test_records = frappe.get_test_records('Salary Slip')
+def make_salary_structure(sal_struct):
+ if not frappe.db.exists('Salary Structure', sal_struct):
+ frappe.get_doc({
+ "doctype": "Salary Structure",
+ "name": sal_struct,
+ "company": erpnext.get_default_company(),
+ "from_date": nowdate(),
+ "employees": get_employee_details(),
+ "earnings": get_earnings_component(),
+ "deductions": get_deductions_component()
+ }).insert()
+ return sal_struct
+
+
+def get_employee_details():
+ return [{"employee": frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"),
+ "base": 25000,
+ "variable": 5000
+ }
+ ]
+
+def get_earnings_component():
+ return [
+ {
+ "salary_component": 'Basic Salary',
+ "abbr":'BS',
+ "condition": 'base > 10000',
+ "formula": 'base*.2',
+ "idx": 1
+ },
+ {
+ "salary_component": 'Basic Salary',
+ "abbr":'BS',
+ "condition": 'base < 10000',
+ "formula": 'base*.1',
+ "idx": 2
+ },
+ {
+ "salary_component": 'HRA',
+ "abbr":'H',
+ "amount": 3000,
+ "idx": 3
+ },
+ {
+ "salary_component": 'Allowance',
+ "abbr":'A',
+ "condition": 'H < 10000',
+ "formula": 'BS*.5',
+ "idx": 4
+ },
+ ]
+
+def get_deductions_component():
+ return [
+ {
+ "salary_component": 'Professional Tax',
+ "abbr":'PT',
+ "condition": 'base > 10000',
+ "formula": 'base*.2',
+ "idx": 1
+ },
+ {
+ "salary_component": 'TDS',
+ "abbr":'T',
+ "formula": 'base*.5',
+ "idx": 2
+ },
+ {
+ "salary_component": 'TDS',
+ "abbr":'T',
+ "condition": 'employment_type=="Intern"',
+ "formula": 'base*.1',
+ "idx": 3
+ }
+ ]
+
+test_dependencies = ["Leave Application", "Holiday List"]
+
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js
index fab7ac4c501..a108e38a0d2 100755
--- a/erpnext/hr/doctype/salary_structure/salary_structure.js
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.js
@@ -9,15 +9,7 @@ cur_frm.cscript.onload = function(doc, dt, dn){
e_tbl = doc.earnings || [];
d_tbl = doc.deductions || [];
if (e_tbl.length == 0 && d_tbl.length == 0)
- return $c_obj(doc,'make_earn_ded_table','', function(r, rt) { refresh_many(['earnings', 'deductions']);});
-}
-
-cur_frm.cscript.refresh = function(doc, dt, dn){
- if((!doc.__islocal) && (doc.is_active == 'Yes') && cint(doc.salary_slip_based_on_timesheet == 0)){
- cur_frm.add_custom_button(__('Salary Slip'),
- cur_frm.cscript['Make Salary Slip'], __("Make"));
- cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
- }
+ return function(r, rt) { refresh_many(['earnings', 'deductions']);};
}
frappe.ui.form.on('Salary Structure', {
@@ -25,29 +17,54 @@ frappe.ui.form.on('Salary Structure', {
frm.trigger("toggle_fields")
frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false);
frm.fields_dict['deductions'].grid.set_column_disp("default_amount", false);
- },
+
+ frm.add_custom_button(__("Preview Salary Slip"),
+ function() { frm.trigger('preview_salary_slip'); }, "icon-sitemap", "btn-default");
+
+ },
salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields")
},
-
+
+ preview_salary_slip: function(frm) {
+ var d = new frappe.ui.Dialog({
+ title: __("Preview Salary Slip"),
+ fields: [
+ {"fieldname":"employee", "fieldtype":"Select", "label":__("Employee"),
+ options: $.map(frm.doc.employees, function(d) { return d.employee }), reqd: 1, label:"Employee"},
+ {fieldname:"fetch", "label":__("Show Salary Slip"), "fieldtype":"Button"}
+ ]
+ });
+ d.get_input("fetch").on("click", function() {
+ var values = d.get_values();
+ if(!values) return;
+ frm.doc.salary_slip_based_on_timesheet?print_format="Salary Slip based on Timesheet":print_format="Salary Slip Standard";
+
+ frappe.call({
+ method: "erpnext.hr.doctype.salary_structure.salary_structure.make_salary_slip",
+ args: {
+ source_name: frm.doc.name,
+ employee: values.employee,
+ as_print: 1,
+ print_format: print_format
+ },
+ callback: function(r) {
+ var new_window = window.open();
+ new_window.document.write(r.message);
+ // frappe.msgprint(r.message);
+ }
+ });
+ });
+ d.show();
+ },
+
toggle_fields: function(frm) {
frm.toggle_display(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet);
frm.toggle_reqd(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet);
}
})
-cur_frm.cscript['Make Salary Slip'] = function() {
- frappe.model.open_mapped_doc({
- method: "erpnext.hr.doctype.salary_structure.salary_structure.make_salary_slip",
- frm: cur_frm
- });
-}
-
-cur_frm.cscript.employee = function(doc, dt, dn){
- if (doc.employee)
- return get_server_fields('get_employee_details','','',doc,dt,dn);
-}
cur_frm.cscript.amount = function(doc, cdt, cdn){
calculate_totals(doc, cdt, cdn);
@@ -79,10 +96,6 @@ cur_frm.cscript.validate = function(doc, cdt, cdn) {
if(doc.employee && doc.is_active == "Yes") frappe.model.clear_doc("Employee", doc.employee);
}
-cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
- return{ query: "erpnext.controllers.queries.employee_query" }
-}
-
frappe.ui.form.on('Salary Detail', {
amount: function(frm) {
@@ -96,4 +109,12 @@ frappe.ui.form.on('Salary Detail', {
deductions_remove: function(frm) {
calculate_totals(frm.doc);
}
-})
\ No newline at end of file
+})
+
+frappe.ui.form.on('Salary Structure Employee', {
+ onload: function(frm) {
+ frm.set_query("employee","employees", function(doc,cdt,cdn) {
+ return{ query: "erpnext.controllers.queries.employee_query" }
+ })
+ }
+});
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.json b/erpnext/hr/doctype/salary_structure/salary_structure.json
index 4cdc67b7f0e..7ee42049f96 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.json
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.json
@@ -2,6 +2,7 @@
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
+ "autoname": "Prompt",
"beta": 0,
"creation": "2013-03-07 18:50:29",
"custom": 0,
@@ -34,140 +35,6 @@
"unique": 0,
"width": "50%"
},
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "employee",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "label": "Employee",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "employee",
- "oldfieldtype": "Link",
- "options": "Employee",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "employee_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Employee Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "employee_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "branch",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "label": "Branch",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "branch",
- "oldfieldtype": "Select",
- "options": "Branch",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "designation",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "label": "Designation",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "designation",
- "oldfieldtype": "Select",
- "options": "Designation",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "department",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "label": "Department",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "department",
- "oldfieldtype": "Select",
- "options": "Department",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
{
"allow_on_submit": 0,
"bold": 0,
@@ -350,6 +217,57 @@
"set_only_once": 0,
"unique": 0
},
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "employee_break",
+ "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,
+ "description": "Select employees for current Salary Structure",
+ "fieldname": "employees",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Employees",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Salary Structure Employee",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
{
"allow_on_submit": 0,
"bold": 0,
@@ -498,6 +416,7 @@
"oldfieldname": "earning_deduction",
"oldfieldtype": "Section Break",
"permlevel": 0,
+ "precision": "2",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -671,7 +590,7 @@
"collapsible": 0,
"fieldname": "total_earning",
"fieldtype": "Currency",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -698,7 +617,7 @@
"collapsible": 0,
"fieldname": "total_deduction",
"fieldtype": "Currency",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -719,37 +638,13 @@
"set_only_once": 0,
"unique": 0
},
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "column_break3",
- "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,
- "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,
- "width": "50%"
- },
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "net_pay",
"fieldtype": "Currency",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -780,7 +675,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-07-13 23:56:01.550518",
+ "modified": "2016-08-10 12:18:31.521436",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Structure",
@@ -832,7 +727,7 @@
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "timeline_field": "employee",
- "title_field": "employee_name",
+ "timeline_field": "",
+ "title_field": "",
"track_seen": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py
index c2f95af9049..d4bc6e3d666 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import cstr, flt, getdate
+from frappe.utils import cstr, flt, getdate, cint
from frappe.model.naming import make_autoname
from frappe import _
from frappe.model.mapper import get_mapped_doc
@@ -12,29 +12,12 @@ from frappe.model.document import Document
from erpnext.hr.utils import set_employee_name
class SalaryStructure(Document):
- def autoname(self):
- self.name = make_autoname(self.employee + '/.SST' + '/.#####')
-
+
def validate(self):
- self.check_overlap()
self.validate_amount()
- self.validate_employee()
self.validate_joining_date()
- set_employee_name(self)
-
- def get_employee_details(self):
- ret = {}
- det = frappe.db.sql("""select employee_name, branch, designation, department
- from `tabEmployee` where name = %s""", self.employee)
- if det:
- ret = {
- 'employee_name': cstr(det[0][0]),
- 'branch': cstr(det[0][1]),
- 'designation': cstr(det[0][2]),
- 'department': cstr(det[0][3]),
- 'backup_employee': cstr(self.employee)
- }
- return ret
+ for e in self.get('employees'):
+ set_employee_name(e)
def get_ss_values(self,employee):
basic_info = frappe.db.sql("""select bank_name, bank_ac_no
@@ -43,71 +26,23 @@ class SalaryStructure(Document):
'bank_ac_no': basic_info and basic_info[0][1] or ''}
return ret
- def make_table(self, doct_name, tab_fname, tab_name):
- list1 = frappe.db.sql("select name from `tab%s` where docstatus != 2" % doct_name)
- for li in list1:
- child = self.append(tab_fname, {})
- if(tab_fname == 'earnings'):
- child.salary_component = cstr(li[0])
- child.amount = 0
- elif(tab_fname == 'deductions'):
- child.salary_component = cstr(li[0])
- child.amount = 0
-
- def make_earn_ded_table(self):
- self.make_table('Salary Component','earnings','Salary Detail')
- self.make_table('Salary Component','deductions', 'Salary Detail')
-
- def check_overlap(self):
- existing = frappe.db.sql("""select name from `tabSalary Structure`
- where employee = %(employee)s and
- (
- (%(from_date)s > from_date and %(from_date)s < to_date) or
- (%(to_date)s > from_date and %(to_date)s < to_date) or
- (%(from_date)s <= from_date and %(to_date)s >= to_date))
- and name!=%(name)s
- and docstatus < 2""",
- {
- "employee": self.employee,
- "from_date": self.from_date,
- "to_date": self.to_date,
- "name": self.name or "No Name"
- }, as_dict=True)
-
- if existing:
- frappe.throw(_("Salary structure {0} already exist, more than one salary structure for same period is not allowed").format(existing[0].name))
-
def validate_amount(self):
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:
frappe.throw(_("Net pay cannot be negative"))
- def validate_employee(self):
- old_employee = frappe.db.get_value("Salary Structure", self.name, "employee")
- if old_employee and self.employee != old_employee:
- frappe.throw(_("Employee can not be changed"))
-
def validate_joining_date(self):
- joining_date = getdate(frappe.db.get_value("Employee", self.employee, "date_of_joining"))
- if getdate(self.from_date) < joining_date:
- frappe.throw(_("From Date in Salary Structure cannot be lesser than Employee Joining Date."))
+ for e in self.get('employees'):
+ joining_date = getdate(frappe.db.get_value("Employee", e.employee, "date_of_joining"))
+ if getdate(self.from_date) < joining_date:
+ frappe.throw(_("From Date in Salary Structure cannot be lesser than Employee Joining Date."))
+
@frappe.whitelist()
-def make_salary_slip(source_name, target_doc=None):
+def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None):
def postprocess(source, target):
- # copy earnings and deductions table
- for key in ('earnings', 'deductions'):
- for d in source.get(key):
- target.append(key, {
- 'amount': d.amount,
- 'default_amount': d.amount,
- 'depends_on_lwp' : d.depends_on_lwp,
- 'salary_component' : d.salary_component
- })
-
- target.run_method("pull_emp_details")
- target.run_method("get_leave_details")
- target.run_method("calculate_net_pay")
-
+ if employee:
+ target.employee = employee
+ target.run_method('process_salary_structure')
doc = get_mapped_doc("Salary Structure", source_name, {
"Salary Structure": {
@@ -119,4 +54,8 @@ def make_salary_slip(source_name, target_doc=None):
}
}, target_doc, postprocess, ignore_child_tables=True)
- return doc
+ if cint(as_print):
+ doc.name = 'Preview for {0}'.format(employee)
+ return frappe.get_print(doc.doctype, doc.name, doc = doc, print_format = print_format)
+ else:
+ return doc
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure/test_records.json b/erpnext/hr/doctype/salary_structure/test_records.json
deleted file mode 100644
index 08841d134a2..00000000000
--- a/erpnext/hr/doctype/salary_structure/test_records.json
+++ /dev/null
@@ -1,24 +0,0 @@
-[
- {
- "doctype": "Salary Structure",
- "name": "_Test Salary Structure 1",
- "employee": "_T-Employee-0001",
- "from_date": "2014-02-01",
- "earnings": [
- {
- "salary_component": "_Test Basic Salary"
- },
- {
- "salary_component": "_Test Allowance"
- }
- ],
- "deductions": [
- {
- "salary_component": "_Test Professional Tax"
- },
- {
- "salary_component": "_Test TDS"
- }
- ]
- }
-]
diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
index 63d0eeddf9c..ef9231ae637 100644
--- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
@@ -4,8 +4,183 @@ from __future__ import unicode_literals
import frappe
import unittest
-
-test_records = frappe.get_test_records('Salary Structure')
+import erpnext
+from frappe.utils import nowdate, add_days, add_years
+from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
+# test_records = frappe.get_test_records('Salary Structure')
class TestSalaryStructure(unittest.TestCase):
- pass
+ def test_setup(self):
+ if not frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2016"):
+ fy = frappe.get_doc({
+ "doctype": "Fiscal Year",
+ "year": "_Test Fiscal Year 2016",
+ "year_end_date": "2016-12-31",
+ "year_start_date": "2016-01-01"
+ })
+ fy.insert()
+
+ self.make_holiday_list()
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List")
+ self.make_salary_component(["Basic Salary", "Allowance", "HRA", "Professional Tax", "TDS"])
+ employee1 = self.make_employee("test_employee@salary.com")
+ employee2 = self.make_employee("test_employee_2@salary.com")
+
+ def make_holiday_list(self):
+ if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"):
+ holiday_list = frappe.get_doc({
+ "doctype": "Holiday List",
+ "holiday_list_name": "Salary Structure Test Holiday List",
+ "from_date": nowdate(),
+ "to_date": add_years(nowdate(), 1),
+ "weekly_off": "Sunday"
+ }).insert()
+ holiday_list.get_weekly_off_dates()
+ holiday_list.save()
+
+ def make_employee(self, user):
+ if not frappe.db.get_value("User", user):
+ frappe.get_doc({
+ "doctype": "User",
+ "email": user,
+ "first_name": user,
+ "new_password": "password",
+ "user_roles": [{"doctype": "UserRole", "role": "Employee"}]
+ }).insert()
+
+
+ if not frappe.db.get_value("Employee", {"user_id": user}):
+ emp = frappe.get_doc({
+ "doctype": "Employee",
+ "naming_series": "EMP-",
+ "employee_name": user,
+ "company": erpnext.get_default_company(),
+ "user_id": user,
+ "date_of_birth": "1990-05-08",
+ "date_of_joining": "2013-01-01",
+ "relieving_date": "",
+ "department": frappe.get_all("Department", fields="name")[0].name,
+ "gender": "Female",
+ "company_email": user,
+ "status": "Active",
+ "employment_type": "Intern"
+ }).insert()
+ return emp.name
+ else:
+ return frappe.get_value("Employee", {"employee_name":user}, "name")
+
+ def make_salary_component(self, salary_components):
+ for salary_component in salary_components:
+ if not frappe.db.exists('Salary Component', salary_component):
+ sal_comp = frappe.get_doc({
+ "doctype": "Salary Component",
+ "salary_component": salary_component
+ })
+ sal_comp.insert()
+
+
+
+ def test_amount_totals(self):
+ sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee@salary.com"})
+ if not sal_slip:
+ sal_slip = make_salary_slip_from_salary_structure(employee=frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}))
+ self.assertEquals(sal_slip.get("salary_structure"), 'Salary Structure Sample')
+ self.assertEquals(sal_slip.get("earnings")[0].amount, 0)
+ self.assertEquals(sal_slip.get("deductions")[0].amount, 0)
+ self.assertEquals(sal_slip.get("deductions")[1].amount, 0)
+ self.assertEquals(sal_slip.get("total_deduction"), 0)
+ self.assertEquals(sal_slip.get("net_pay"), 0)
+
+
+def make_salary_slip_from_salary_structure(employee):
+ sal_struct = make_salary_structure('Salary Structure Sample')
+ sal_slip = make_salary_slip(sal_struct, employee = employee)
+ sal_slip.employee_name = frappe.get_value("Employee", {"name":employee}, "employee_name")
+ sal_slip.month = "11"
+ sal_slip.fiscal_year = "_Test Fiscal Year 2016"
+ sal_slip.insert()
+ sal_slip.submit()
+ return sal_slip
+
+def make_salary_structure(sal_struct):
+ if not frappe.db.exists('Salary Structure', sal_struct):
+ frappe.get_doc({
+ "doctype": "Salary Structure",
+ "name": sal_struct,
+ "company": erpnext.get_default_company(),
+ "from_date": nowdate(),
+ "employees": get_employee_details(),
+ "earnings": get_earnings_component(),
+ "deductions": get_deductions_component()
+ }).insert()
+ return sal_struct
+
+
+def get_employee_details():
+ return [{"employee": frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"),
+ "base": 25000,
+ "variable": 5000,
+ "idx": 1
+ },
+ {"employee": frappe.get_value("Employee", {"employee_name":"test_employee_2@salary.com"}, "name"),
+ "base": 2100,
+ "variable": 100,
+ "idx": 2
+ }
+ ]
+
+def get_earnings_component():
+ return [
+ {
+ "salary_component": 'Basic Salary',
+ "abbr":'BS',
+ "condition": 'base > 10000',
+ "formula": 'base*.2',
+ "idx": 1
+ },
+ {
+ "salary_component": 'Basic Salary',
+ "abbr":'BS',
+ "condition": 'base < 10000',
+ "formula": 'base*.1',
+ "idx": 2
+ },
+ {
+ "salary_component": 'HRA',
+ "abbr":'H',
+ "amount": 3000,
+ "idx": 3
+ },
+ {
+ "salary_component": 'Allowance',
+ "abbr":'A',
+ "condition": 'H < 10000',
+ "formula": 'BS*.5',
+ "idx": 4
+ },
+ ]
+
+def get_deductions_component():
+ return [
+ {
+ "salary_component": 'Professional Tax',
+ "abbr":'PT',
+ "condition": 'base > 10000',
+ "formula": 'base*.2',
+ "idx": 1
+ },
+ {
+ "salary_component": 'TDS',
+ "abbr":'T',
+ "formula": 'base*.5',
+ "idx": 2
+ },
+ {
+ "salary_component": 'TDS',
+ "abbr":'T',
+ "condition": 'employment_type=="Intern"',
+ "formula": 'base*.1',
+ "idx": 3
+ }
+ ]
+
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure_employee/__init__.py b/erpnext/hr/doctype/salary_structure_employee/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.json b/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.json
new file mode 100644
index 00000000000..aae33e3cac5
--- /dev/null
+++ b/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.json
@@ -0,0 +1,139 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "employee",
+ "beta": 0,
+ "creation": "2016-07-26 11:53:43.621605",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Employee",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Employee",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Employee Name",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "base",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Base",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "variable",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Variable",
+ "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
+ }
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2016-08-11 12:18:14.526977",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Salary Structure Employee",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.py b/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.py
new file mode 100644
index 00000000000..dfcac3f903b
--- /dev/null
+++ b/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class SalaryStructureEmployee(Document):
+ pass
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ef9f4da5ab7..e383929ecd6 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -310,4 +310,5 @@ erpnext.patches.v7_0.set_material_request_type_in_item
erpnext.patches.v7_0.rename_examination_to_assessment
erpnext.patches.v7_0.set_portal_settings
erpnext.patches.v7_0.repost_future_gle_for_purchase_invoice
-erpnext.patches.v7_0.fix_duplicate_icons
\ No newline at end of file
+erpnext.patches.v7_0.fix_duplicate_icons
+erpnext.patches.v7_0.move_employee_parent_to_child_in_salary_structure
\ No newline at end of file
diff --git a/erpnext/patches/v7_0/move_employee_parent_to_child_in_salary_structure.py b/erpnext/patches/v7_0/move_employee_parent_to_child_in_salary_structure.py
new file mode 100644
index 00000000000..085fbbf52f5
--- /dev/null
+++ b/erpnext/patches/v7_0/move_employee_parent_to_child_in_salary_structure.py
@@ -0,0 +1,10 @@
+import frappe
+
+def execute():
+ frappe.reload_doc('hr', 'doctype', 'salary_structure')
+ for ss in frappe.db.sql(""" select employee, name from `tabSalary Structure`""", as_dict=True):
+ ss_doc = frappe.get_doc('Salary Structure', ss.name)
+ se = ss_doc.append('employees',{})
+ se.employee = ss.employee
+ se.base = 0
+ ss_doc.save()
\ No newline at end of file
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 300933aac66..e443435d58e 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -27,7 +27,7 @@ class TestTimesheet(unittest.TestCase):
self.assertEquals(salary_slip.total_working_hours, 2)
self.assertEquals(salary_slip.hour_rate, 50)
- self.assertEquals(salary_slip.net_pay, 150)
+ self.assertEquals(salary_slip.net_pay, 50)
self.assertEquals(salary_slip.timesheets[0].time_sheet, timesheet.name)
self.assertEquals(salary_slip.timesheets[0].working_hours, 2)
@@ -54,35 +54,42 @@ class TestTimesheet(unittest.TestCase):
timesheet = frappe.get_doc('Timesheet', timesheet.name)
self.assertEquals(sales_invoice.total_billing_amount, 100)
self.assertEquals(timesheet.status, 'Billed')
+
def make_salary_structure(employee):
- name = frappe.db.get_value('Salary Structure', {'employee': employee, 'salary_slip_based_on_timesheet': 1}, 'name')
+ name = frappe.db.get_value('Salary Structure Employee', {'employee': employee}, 'parent')
if name:
salary_structure = frappe.get_doc('Salary Structure', name)
else:
salary_structure = frappe.new_doc("Salary Structure")
+ salary_structure.name = "Timesheet Salary Structure Test"
+ salary_structure.salary_slip_based_on_timesheet = 1
+ salary_structure.from_date = nowdate()
+ salary_structure.salary_component = "Basic"
+ salary_structure.hour_rate = 50.0
+ salary_structure.company= "_Test Company"
- salary_structure.salary_slip_based_on_timesheet = 1
- salary_structure.employee = employee
- salary_structure.from_date = nowdate()
- salary_structure.salary_component = "Basic"
- salary_structure.hour_rate = 50.0
- salary_structure.company= "_Test Company"
+ salary_structure.set('employees', [])
+ salary_structure.set('earnings', [])
+ salary_structure.set('deductions', [])
- salary_structure.set('earnings', [])
- salary_structure.set('deductions', [])
+ es = salary_structure.append('employees', {
+ "employee": employee,
+ "base": 1200
+ })
+
+
+ es = salary_structure.append('earnings', {
+ "salary_component": "_Test Allowance",
+ "amount": 100
+ })
- es = salary_structure.append('earnings', {
- "salary_component": "_Test Allowance",
- "amount": 100
- })
+ ds = salary_structure.append('deductions', {
+ "salary_component": "_Test Professional Tax",
+ "amount": 50
+ })
- ds = salary_structure.append('deductions', {
- "salary_component": "_Test Professional Tax",
- "amount": 50
- })
-
- salary_structure.save(ignore_permissions=True)
+ salary_structure.save(ignore_permissions=True)
return salary_structure