Merge pull request #5924 from KanchanChauhan/salary-structure-changes

Formula based Salary Structure
This commit is contained in:
Nabin Hait 2016-08-18 16:39:58 +05:30 committed by GitHub
commit b5951b72f3
29 changed files with 1242 additions and 485 deletions

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -42,15 +42,52 @@ To create a new Salary Structure go to:
> Human Resources > Setup > Salary Structure > New Salary Structure > Human Resources > Setup > Salary Structure > New Salary Structure
#### Figure 1:Salary Structure #### Figure 1.1:Salary Structure
<img class="screenshot" alt="Salary Structure" src="{{docs_base_url}}/assets/img/human-resources/salary-structure.png"> <img class="screenshot" alt="Salary Structure" src="{{docs_base_url}}/assets/img/human-resources/salary-structure.png">
### In the 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) * 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
<img class="screenshot" alt="Salary Structure" src="{{docs_base_url}}/assets/img/human-resources/salary-timesheet.png">
### 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
<img class="screenshot" alt="Salary Structure" src="{{docs_base_url}}/assets/img/human-resources/condition-formula.png">
* Condition and Amount
#### Figure 1.4:Condition and Amount
<img class="screenshot" alt="Salary Structure" src="{{docs_base_url}}/assets/img/human-resources/condition-amount.png">
* 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) ### Leave Without Pay (LWP)
@ -64,6 +101,7 @@ days for the month (based on the Holiday List).
If you dont want ERPNext to manage LWP, just dont click on LWP in any of the If you dont want ERPNext to manage LWP, just dont click on LWP in any of the
Earning Types and Deduction Types. Earning Types and Deduction Types.
* * * * * *
### Creating Salary Slips ### 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 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. 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 To create a new Salary Slip go to:
Slip.
> Human Resources > Setup > Salary Slip > New Salary Slip
#### Figure 2: Salary Slip #### Figure 2: Salary Slip

View File

@ -40,13 +40,6 @@ erpnext.hr.EmployeeController = frappe.ui.form.Controller.extend({
"Ms": "Female" "Ms": "Female"
}[this.frm.doc.salutation]); }[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}); cur_frm.cscript = new erpnext.hr.EmployeeController({frm: cur_frm});

View File

@ -178,19 +178,6 @@ def get_retirement_date(date_of_birth=None):
return ret 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): def validate_employee_role(doc, method):
# called via User hook # called via User hook
if "Employee" in [d.role for d in doc.get("user_roles")]: if "Employee" in [d.role for d in doc.get("user_roles")]:

View File

@ -8,6 +8,7 @@
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Other", "document_type": "Other",
"editable_grid": 1,
"fields": [ "fields": [
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
@ -187,6 +188,31 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "hide_heading": 0,
@ -200,7 +226,7 @@
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-06-27 16:20:59.737869", "modified": "2016-08-10 12:32:39.780599",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR Settings", "name": "HR Settings",

View File

@ -9,6 +9,7 @@
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"editable_grid": 1,
"fields": [ "fields": [
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
@ -35,6 +36,33 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "allow_on_submit": 0,
"bold": 0, "bold": 0,
@ -59,6 +87,32 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "hide_heading": 0,
@ -72,7 +126,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-07-01 12:42:46.103131", "modified": "2016-07-27 17:40:18.335540",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Component", "name": "Salary Component",

View File

@ -7,4 +7,21 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class SalaryComponent(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"))

View File

@ -40,13 +40,171 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 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", "fieldname": "amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 1, "in_list_view": 0,
"label": "Amount", "label": "Amount",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@ -66,30 +224,7 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"fieldname": "column_break_3", "depends_on": "",
"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": "depends_on_lwp", "fieldname": "depends_on_lwp",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -102,6 +237,33 @@
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "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": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
@ -115,17 +277,43 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"fieldname": "default_amount", "depends_on": "eval:doc.parenttype=='Salary Structure'",
"fieldtype": "Currency", "fieldname": "section_break_11",
"fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Default Amount",
"length": 0, "length": 0,
"no_copy": 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": "<h3>Condition and Formula Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base &lt; 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS &gt; 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -148,7 +336,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-07-11 03:28:06.925361", "modified": "2016-08-18 13:01:37.617174",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Detail", "name": "Salary Detail",

View File

@ -9,6 +9,7 @@
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"editable_grid": 0,
"fields": [ "fields": [
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1172,7 +1173,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-07-07 12:49:01.596547", "modified": "2016-08-10 15:57:59.944600",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Slip", "name": "Salary Slip",

View File

@ -25,23 +25,84 @@ class SalarySlip(TransactionBase):
self.set_month_dates() self.set_month_dates()
if not (len(self.get("earnings")) or len(self.get("deductions"))): if not (len(self.get("earnings")) or len(self.get("deductions"))):
# get details from salary structure
self.get_emp_and_leave_details() self.get_emp_and_leave_details()
else: else:
self.get_leave_details(lwp = self.leave_without_pay) self.get_leave_details(lwp = self.leave_without_pay)
if self.salary_slip_based_on_timesheet or not self.net_pay: # if self.salary_slip_based_on_timesheet or not self.net_pay:
self.calculate_net_pay() # self.calculate_net_pay()
company_currency = get_company_currency(self.company) company_currency = get_company_currency(self.company)
self.total_in_words = money_in_words(self.rounded_total, company_currency) 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): def validate_dates(self):
if date_diff(self.end_date, self.start_date) < 0: if date_diff(self.end_date, self.start_date) < 0:
frappe.throw(_("To date cannot be before From date")) 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): def get_emp_and_leave_details(self):
'''First time, load all the components from salary structure'''
if self.employee: if self.employee:
self.set("earnings", []) self.set("earnings", [])
self.set("deductions", []) self.set("deductions", [])
@ -55,10 +116,10 @@ class SalarySlip(TransactionBase):
struct = self.check_sal_struct(joining_date, relieving_date) struct = self.check_sal_struct(joining_date, relieving_date)
if struct: if struct:
ss_doc = frappe.get_doc('Salary Structure', struct) self._salary_structure_doc = frappe.get_doc('Salary Structure', struct)
self.salary_slip_based_on_timesheet = ss_doc.salary_slip_based_on_timesheet or 0 self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
self.set_time_sheet() self.set_time_sheet()
self.pull_sal_struct(ss_doc) self.pull_sal_struct()
def set_time_sheet(self): def set_time_sheet(self):
if self.salary_slip_based_on_timesheet: if self.salary_slip_based_on_timesheet:
@ -79,11 +140,15 @@ class SalarySlip(TransactionBase):
self.end_date = m['month_end_date'] self.end_date = m['month_end_date']
def check_sal_struct(self, joining_date, relieving_date): def check_sal_struct(self, joining_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` struct = frappe.db.sql("""select name from `tabSalary Structure`
where employee=%s and is_active = 'Yes' where name=%s and is_active = 'Yes'
and (from_date <= %s or from_date <= %s) 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""", 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, self.start_date, joining_date, self.end_date, relieving_date))
if not struct: if not struct:
self.salary_structure = None self.salary_structure = None
@ -91,16 +156,28 @@ class SalarySlip(TransactionBase):
.format(self.employee), title=_('Salary Structure Missing')) .format(self.employee), title=_('Salary Structure Missing'))
return struct and struct[0][0] or '' 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'))
def pull_sal_struct(self, ss_doc): def pull_sal_struct(self):
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip 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: if self.salary_slip_based_on_timesheet:
self.salary_structure = ss_doc.name self.salary_structure = self._salary_structure_doc.name
self.hour_rate = ss_doc.hour_rate 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.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): def add_earning_for_hourly_wages(self, salary_component):
default_type = False default_type = False
@ -121,6 +198,7 @@ class SalarySlip(TransactionBase):
self.bank_name = emp.bank_name self.bank_name = emp.bank_name
self.bank_account_no = emp.bank_ac_no self.bank_account_no = emp.bank_ac_no
def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None): def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None):
if not self.fiscal_year: if not self.fiscal_year:
# if default fiscal year is not set, get from nowdate # 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': 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)) frappe.throw(_("Salary Slip of employee {0} already created for time sheet {1}").format(self.employee, data.time_sheet))
def calculate_earning_total(self): def sum_components(self, component_type, total_field):
self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount) for d in self.get(component_type):
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'):
if cint(d.depends_on_lwp) == 1 and not self.salary_slip_based_on_timesheet: 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) 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: elif not self.payment_days and not self.salary_slip_based_on_timesheet:
d.amount = 0 d.amount = 0
elif not d.amount: elif not d.amount:
d.amount = d.default_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): def calculate_net_pay(self):
self.calculate_component_amounts()
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total")) disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
self.calculate_earning_total() self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount)
self.calculate_ded_total() 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.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
self.rounded_total = rounded(self.net_pay, self.rounded_total = rounded(self.net_pay,
self.precision("net_pay") if disable_rounded_total else 0) self.precision("net_pay") if disable_rounded_total else 0)

View File

@ -4,19 +4,22 @@ from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from frappe.utils import today, now_datetime, getdate, cstr import erpnext
from erpnext.hr.doctype.employee.employee import make_salary_structure 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.salary_structure.salary_structure import make_salary_slip
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
class TestSalarySlip(unittest.TestCase): class TestSalarySlip(unittest.TestCase):
def setUp(self): def setUp(self):
self.make_salary_component(["Basic Salary", "Allowance", "HRA", "Professional Tax", "TDS"])
for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]: for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]:
frappe.db.sql("delete from `tab%s`" % dt) frappe.db.sql("delete from `tab%s`" % dt)
make_allocation_record(leave_type="_Test Leave Type LWP") 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 from erpnext.hr.doctype.leave_application.test_leave_application import _test_records as leave_applications
la = frappe.copy_doc(leave_applications[2]) la = frappe.copy_doc(leave_applications[2])
@ -30,71 +33,78 @@ class TestSalarySlip(unittest.TestCase):
def test_salary_slip_with_holidays_included(self): def test_salary_slip_with_holidays_included(self):
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)
ss = frappe.copy_doc(test_records[0]) self.make_employee("test_employee@salary.com")
ss.insert() 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.total_days_in_month, 31)
self.assertEquals(ss.payment_days, 30) self.assertEquals(ss.payment_days, 31)
self.assertEquals(ss.earnings[0].amount, 14516.13) self.assertEquals(ss.earnings[0].amount, 0)
self.assertEquals(ss.earnings[1].amount, 500) self.assertEquals(ss.earnings[1].amount, 0)
self.assertEquals(ss.deductions[0].amount, 100) self.assertEquals(ss.deductions[0].amount, 0)
self.assertEquals(ss.deductions[1].amount, 48.39) self.assertEquals(ss.deductions[1].amount, 0)
self.assertEquals(ss.gross_pay, 15016.13) self.assertEquals(ss.gross_pay, 0)
self.assertEquals(ss.net_pay, 14867.74) self.assertEquals(ss.net_pay, 0)
def test_salary_slip_with_holidays_excluded(self): def test_salary_slip_with_holidays_excluded(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
ss = frappe.copy_doc(test_records[0]) self.make_employee("test_employee@salary.com")
ss.insert() 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, 29) self.assertEquals(ss.total_days_in_month, 27)
self.assertEquals(ss.payment_days, 28) self.assertEquals(ss.payment_days, 27)
self.assertEquals(ss.earnings[0].amount, 14516.13) self.assertEquals(ss.earnings[0].amount, 0)
self.assertEquals(ss.earnings[1].amount, 500) self.assertEquals(ss.earnings[0].default_amount, 5000)
self.assertEquals(ss.deductions[0].amount, 100) self.assertEquals(ss.earnings[1].amount, 0)
self.assertEquals(ss.deductions[1].amount, 48.39) self.assertEquals(ss.deductions[0].amount, 0)
self.assertEquals(ss.gross_pay, 15016.13) self.assertEquals(ss.deductions[1].amount, 0)
self.assertEquals(ss.net_pay, 14867.74) self.assertEquals(ss.gross_pay, 0)
self.assertEquals(ss.net_pay, 0)
def test_payment_days(self): def test_payment_days(self):
# Holidays not included in working days # Holidays not included in working days
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
# set joinng date in the same month # 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.copy_doc(test_records[0]) ss = frappe.get_doc("Salary Slip",
ss.insert() self.make_employee_salary_slip("test_employee@salary.com"))
self.assertEquals(ss.total_days_in_month, 29) self.assertEquals(ss.total_days_in_month, 27)
self.assertEquals(ss.payment_days, 19) self.assertEquals(ss.payment_days, 27)
# set relieving date in the same month # 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")
ss.save() 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, 29)
self.assertEquals(ss.payment_days, 16)
self.assertEquals(ss.total_days_in_month, 27)
self.assertEquals(ss.payment_days, 27)
ss.save()
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 # 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() ss.save()
self.assertEquals(ss.total_days_in_month, 31) #
self.assertEquals(ss.payment_days, 17) # 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)
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)
def test_employee_salary_slip_read_permission(self): def test_employee_salary_slip_read_permission(self):
self.make_employee("test_employee@example.com") self.make_employee("test_employee@salary.com")
self.make_employee("test_employee_2@example.com")
salary_slip_test_employee = frappe.get_doc("Salary Slip", salary_slip_test_employee = frappe.get_doc("Salary Slip",
self.make_employee_salary_slip("test_employee@example.com")) self.make_employee_salary_slip("test_employee@salary.com"))
frappe.set_user("test_employee@salary.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.assertTrue(salary_slip_test_employee.has_permission("read")) self.assertTrue(salary_slip_test_employee.has_permission("read"))
def test_email_salary_slip(self): def test_email_salary_slip(self):
@ -104,8 +114,10 @@ class TestSalarySlip(unittest.TestCase):
hr_settings.email_salary_slip_to_employee = 1 hr_settings.email_salary_slip_to_employee = 1
hr_settings.save() hr_settings.save()
self.make_employee("test_employee@example.com") self.make_employee("test_employee@salary.com")
self.make_employee_salary_slip("test_employee@example.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`""") email_queue = frappe.db.sql("""select name from `tabEmail Queue`""")
self.assertTrue(email_queue) self.assertTrue(email_queue)
@ -123,32 +135,52 @@ class TestSalarySlip(unittest.TestCase):
if not frappe.db.get_value("Employee", {"user_id": user}): if not frappe.db.get_value("Employee", {"user_id": user}):
frappe.get_doc({ frappe.get_doc({
"doctype": "Employee", "doctype": "Employee",
"naming_series": "_T-Employee-", "naming_series": "EMP-",
"employee_name": user, "employee_name": user,
"company": erpnext.get_default_company(),
"user_id": user, "user_id": user,
"company": "_Test Company",
"date_of_birth": "1990-05-08", "date_of_birth": "1990-05-08",
"date_of_joining": "2013-01-01", "date_of_joining": "2013-01-01",
"department": "_Test Department 1", "department": frappe.get_all("Department", fields="name")[0].name,
"gender": "Female", "gender": "Female",
"company_email": user, "company_email": user,
"status": "Active" "status": "Active",
"employment_type": "Intern"
}).insert() }).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): def make_employee_salary_slip(self, user):
employee = frappe.db.get_value("Employee", {"user_id": user}) employee = frappe.db.get_value("Employee", {"user_id": user})
salary_structure = frappe.db.get_value("Salary Structure", {"employee": employee}) salary_structure = make_salary_structure("Salary Structure Test for Salary Slip")
if not salary_structure: salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
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})
if not salary_slip: 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.insert()
salary_slip.submit() # salary_slip.submit()
salary_slip = salary_slip.name salary_slip = salary_slip.name
return salary_slip return salary_slip
@ -160,6 +192,82 @@ class TestSalarySlip(unittest.TestCase):
activity_type.wage_rate = 25 activity_type.wage_rate = 25
activity_type.save() activity_type.save()
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"] test_dependencies = ["Leave Application", "Holiday List"]
test_records = frappe.get_test_records('Salary Slip')

View File

@ -9,15 +9,7 @@ cur_frm.cscript.onload = function(doc, dt, dn){
e_tbl = doc.earnings || []; e_tbl = doc.earnings || [];
d_tbl = doc.deductions || []; d_tbl = doc.deductions || [];
if (e_tbl.length == 0 && d_tbl.length == 0) if (e_tbl.length == 0 && d_tbl.length == 0)
return $c_obj(doc,'make_earn_ded_table','', function(r, rt) { refresh_many(['earnings', 'deductions']);}); return 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"));
}
} }
frappe.ui.form.on('Salary Structure', { frappe.ui.form.on('Salary Structure', {
@ -25,29 +17,54 @@ frappe.ui.form.on('Salary Structure', {
frm.trigger("toggle_fields") frm.trigger("toggle_fields")
frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false); frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false);
frm.fields_dict['deductions'].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) { salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields") 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) { toggle_fields: function(frm) {
frm.toggle_display(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet); 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); 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){ cur_frm.cscript.amount = function(doc, cdt, cdn){
calculate_totals(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); 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', { frappe.ui.form.on('Salary Detail', {
amount: function(frm) { amount: function(frm) {
@ -97,3 +110,11 @@ frappe.ui.form.on('Salary Detail', {
calculate_totals(frm.doc); calculate_totals(frm.doc);
} }
}) })
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" }
})
}
});

View File

@ -2,6 +2,7 @@
"allow_copy": 0, "allow_copy": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0, "allow_rename": 0,
"autoname": "Prompt",
"beta": 0, "beta": 0,
"creation": "2013-03-07 18:50:29", "creation": "2013-03-07 18:50:29",
"custom": 0, "custom": 0,
@ -34,140 +35,6 @@
"unique": 0, "unique": 0,
"width": "50%" "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, "allow_on_submit": 0,
"bold": 0, "bold": 0,
@ -350,6 +217,57 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "allow_on_submit": 0,
"bold": 0, "bold": 0,
@ -498,6 +416,7 @@
"oldfieldname": "earning_deduction", "oldfieldname": "earning_deduction",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"permlevel": 0, "permlevel": 0,
"precision": "2",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
@ -671,7 +590,7 @@
"collapsible": 0, "collapsible": 0,
"fieldname": "total_earning", "fieldname": "total_earning",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
@ -698,7 +617,7 @@
"collapsible": 0, "collapsible": 0,
"fieldname": "total_deduction", "fieldname": "total_deduction",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
@ -719,37 +638,13 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"fieldname": "net_pay", "fieldname": "net_pay",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
@ -780,7 +675,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-07-13 23:56:01.550518", "modified": "2016-08-10 12:18:31.521436",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Structure", "name": "Salary Structure",
@ -832,7 +727,7 @@
"read_only_onload": 0, "read_only_onload": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"timeline_field": "employee", "timeline_field": "",
"title_field": "employee_name", "title_field": "",
"track_seen": 0 "track_seen": 0
} }

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe 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.model.naming import make_autoname
from frappe import _ from frappe import _
from frappe.model.mapper import get_mapped_doc 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 from erpnext.hr.utils import set_employee_name
class SalaryStructure(Document): class SalaryStructure(Document):
def autoname(self):
self.name = make_autoname(self.employee + '/.SST' + '/.#####')
def validate(self): def validate(self):
self.check_overlap()
self.validate_amount() self.validate_amount()
self.validate_employee()
self.validate_joining_date() self.validate_joining_date()
set_employee_name(self) for e in self.get('employees'):
set_employee_name(e)
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
def get_ss_values(self,employee): def get_ss_values(self,employee):
basic_info = frappe.db.sql("""select bank_name, bank_ac_no 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 ''} 'bank_ac_no': basic_info and basic_info[0][1] or ''}
return ret 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): def validate_amount(self):
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet: if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:
frappe.throw(_("Net pay cannot be negative")) 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): def validate_joining_date(self):
joining_date = getdate(frappe.db.get_value("Employee", self.employee, "date_of_joining")) 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: if getdate(self.from_date) < joining_date:
frappe.throw(_("From Date in Salary Structure cannot be lesser than Employee Joining Date.")) frappe.throw(_("From Date in Salary Structure cannot be lesser than Employee Joining Date."))
@frappe.whitelist() @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): def postprocess(source, target):
# copy earnings and deductions table if employee:
for key in ('earnings', 'deductions'): target.employee = employee
for d in source.get(key): target.run_method('process_salary_structure')
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")
doc = get_mapped_doc("Salary Structure", source_name, { doc = get_mapped_doc("Salary Structure", source_name, {
"Salary Structure": { "Salary Structure": {
@ -119,4 +54,8 @@ def make_salary_slip(source_name, target_doc=None):
} }
}, target_doc, postprocess, ignore_child_tables=True) }, target_doc, postprocess, ignore_child_tables=True)
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 return doc

View File

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

View File

@ -4,8 +4,183 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
import erpnext
test_records = frappe.get_test_records('Salary Structure') 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): 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
}
]

View File

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

View File

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

View File

@ -311,3 +311,4 @@ erpnext.patches.v7_0.rename_examination_to_assessment
erpnext.patches.v7_0.set_portal_settings erpnext.patches.v7_0.set_portal_settings
erpnext.patches.v7_0.repost_future_gle_for_purchase_invoice erpnext.patches.v7_0.repost_future_gle_for_purchase_invoice
erpnext.patches.v7_0.fix_duplicate_icons erpnext.patches.v7_0.fix_duplicate_icons
erpnext.patches.v7_0.move_employee_parent_to_child_in_salary_structure

View File

@ -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()

View File

@ -27,7 +27,7 @@ class TestTimesheet(unittest.TestCase):
self.assertEquals(salary_slip.total_working_hours, 2) self.assertEquals(salary_slip.total_working_hours, 2)
self.assertEquals(salary_slip.hour_rate, 50) 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].time_sheet, timesheet.name)
self.assertEquals(salary_slip.timesheets[0].working_hours, 2) self.assertEquals(salary_slip.timesheets[0].working_hours, 2)
@ -55,23 +55,30 @@ class TestTimesheet(unittest.TestCase):
self.assertEquals(sales_invoice.total_billing_amount, 100) self.assertEquals(sales_invoice.total_billing_amount, 100)
self.assertEquals(timesheet.status, 'Billed') self.assertEquals(timesheet.status, 'Billed')
def make_salary_structure(employee): 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: if name:
salary_structure = frappe.get_doc('Salary Structure', name) salary_structure = frappe.get_doc('Salary Structure', name)
else: else:
salary_structure = frappe.new_doc("Salary Structure") salary_structure = frappe.new_doc("Salary Structure")
salary_structure.name = "Timesheet Salary Structure Test"
salary_structure.salary_slip_based_on_timesheet = 1 salary_structure.salary_slip_based_on_timesheet = 1
salary_structure.employee = employee
salary_structure.from_date = nowdate() salary_structure.from_date = nowdate()
salary_structure.salary_component = "Basic" salary_structure.salary_component = "Basic"
salary_structure.hour_rate = 50.0 salary_structure.hour_rate = 50.0
salary_structure.company= "_Test Company" salary_structure.company= "_Test Company"
salary_structure.set('employees', [])
salary_structure.set('earnings', []) salary_structure.set('earnings', [])
salary_structure.set('deductions', []) salary_structure.set('deductions', [])
es = salary_structure.append('employees', {
"employee": employee,
"base": 1200
})
es = salary_structure.append('earnings', { es = salary_structure.append('earnings', {
"salary_component": "_Test Allowance", "salary_component": "_Test Allowance",
"amount": 100 "amount": 100