[fix] demo and process payroll for demo (#7224)

* [fix] demo and process payroll for demo

* [fix] set payroll dates
This commit is contained in:
Rushabh Mehta
2016-12-15 11:27:35 +05:30
committed by GitHub
parent 1ae8e2c8fa
commit e9d9b8e6f0
18 changed files with 338 additions and 298 deletions

View File

@@ -2,7 +2,7 @@ from __future__ import unicode_literals
import random, json import random, json
import frappe, erpnext import frappe, erpnext
from frappe.utils import flt, now_datetime, cstr from frappe.utils import flt, now_datetime, cstr, random_string
from frappe.utils.make_random import add_random_children, get_random from frappe.utils.make_random import add_random_children, get_random
from erpnext.demo.domains import data from erpnext.demo.domains import data
from frappe import _ from frappe import _
@@ -14,8 +14,15 @@ def setup(domain):
setup_holiday_list() setup_holiday_list()
setup_user() setup_user()
setup_employee() setup_employee()
setup_salary_structure()
setup_salary_structure_for_timesheet() employees = frappe.get_all('Employee', fields=['name', 'date_of_joining'])
# monthly salary
setup_salary_structure(employees[:5], 0)
# based on timesheet
setup_salary_structure(employees[5:], 1)
setup_leave_allocation() setup_leave_allocation()
setup_user_roles() setup_user_roles()
setup_customer() setup_customer()
@@ -111,30 +118,44 @@ def setup_employee():
frappe.db.set_value("HR Settings", None, "emp_created_by", "Naming Series") frappe.db.set_value("HR Settings", None, "emp_created_by", "Naming Series")
frappe.db.commit() frappe.db.commit()
for d in frappe.get_all('Salary Component'):
salary_component = frappe.get_doc('Salary Component', d.name)
salary_component.append('accounts', dict(
company=erpnext.get_default_company(),
default_account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
))
salary_component.save()
import_json('Employee') import_json('Employee')
def setup_salary_structure(): def setup_salary_structure(employees, salary_slip_based_on_timesheet=0):
f = frappe.get_doc('Fiscal Year', frappe.defaults.get_global_default('fiscal_year')) f = frappe.get_doc('Fiscal Year', frappe.defaults.get_global_default('fiscal_year'))
ss = frappe.new_doc('Salary Structure') ss = frappe.new_doc('Salary Structure')
ss.name = "Sample Salary Structure - " + str(f.year_start_date) ss.name = "Sample Salary Structure - " + random_string(5)
for e in frappe.get_all('Employee', fields=['name', 'date_of_joining']): for e in employees:
ss.append('employees', { ss.append('employees', {
'employee': e.name, 'employee': e.name,
'base': random.random() * 10000 'base': random.random() * 10000
}) })
if not e.date_of_joining: ss.from_date = e.date_of_joining if (e.date_of_joining
continue and e.date_of_joining > f.year_start_date) else f.year_start_date
ss.to_date = f.year_end_date
ss.salary_slip_based_on_timesheet = salary_slip_based_on_timesheet
if salary_slip_based_on_timesheet:
ss.salary_component = 'Basic'
ss.hour_rate = flt(random.random() * 10, 2)
else:
ss.payroll_frequency = 'Monthly'
ss.payment_account = frappe.get_value('Account',
{'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
ss.from_date = e.date_of_joining if (e.date_of_joining
and e.date_of_joining > f.year_start_date) else f.year_start_date
ss.to_date = f.year_end_date
ss.payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
ss.append('earnings', { ss.append('earnings', {
'salary_component': 'Basic', 'salary_component': 'Basic',
"abbr":'B', "abbr":'B',
'condition': 'base > 5000',
'formula': 'base*.2', 'formula': 'base*.2',
'amount_based_on_formula': 1, 'amount_based_on_formula': 1,
"idx": 1 "idx": 1
@@ -142,20 +163,14 @@ def setup_salary_structure():
ss.append('deductions', { ss.append('deductions', {
'salary_component': 'Income Tax', 'salary_component': 'Income Tax',
"abbr":'IT', "abbr":'IT',
'condition': 'base > 5000', 'condition': 'base > 1000',
'amount': random.random() * 1000, 'amount': random.random() * 1000,
"idx": 1 "idx": 1
}) })
ss.insert() ss.insert()
def setup_salary_structure_for_timesheet(): return ss
for e in frappe.get_all('Salary Structure', fields=['name'], filters={'is_active': 'Yes'}, limit=2):
ss_doc = frappe.get_doc("Salary Structure", e.name)
ss_doc.salary_slip_based_on_timesheet = 1
ss_doc.salary_component = 'Basic'
ss_doc.hour_rate = flt(random.random() * 10, 2)
ss_doc.save(ignore_permissions=True)
def setup_user_roles(): def setup_user_roles():
user = frappe.get_doc('User', 'demo@erpnext.com') user = frappe.get_doc('User', 'demo@erpnext.com')

View File

@@ -23,25 +23,32 @@ def work():
report = "Ordered Items to be Billed" report = "Ordered Items to be Billed"
for so in list(set([r[0] for r in query_report.run(report)["result"] for so in list(set([r[0] for r in query_report.run(report)["result"]
if r[0]!="Total"]))[:random.randint(1, 5)]: if r[0]!="Total"]))[:random.randint(1, 5)]:
si = frappe.get_doc(make_sales_invoice(so)) try:
si.posting_date = frappe.flags.current_date si = frappe.get_doc(make_sales_invoice(so))
for d in si.get("items"): si.posting_date = frappe.flags.current_date
if not d.income_account: for d in si.get("items"):
d.income_account = "Sales - {}".format(frappe.db.get_value('Company', si.company, 'abbr')) if not d.income_account:
si.insert() d.income_account = "Sales - {}".format(frappe.db.get_value('Company', si.company, 'abbr'))
si.submit() si.insert()
frappe.db.commit() si.submit()
frappe.db.commit()
except frappe.ValidationError:
pass
if random.random() <= 0.6: if random.random() <= 0.6:
report = "Received Items to be Billed" report = "Received Items to be Billed"
for pr in list(set([r[0] for r in query_report.run(report)["result"] for pr in list(set([r[0] for r in query_report.run(report)["result"]
if r[0]!="Total"]))[:random.randint(1, 5)]: if r[0]!="Total"]))[:random.randint(1, 5)]:
pi = frappe.get_doc(make_purchase_invoice(pr)) try:
pi.posting_date = frappe.flags.current_date pi = frappe.get_doc(make_purchase_invoice(pr))
pi.bill_no = random_string(6) pi.posting_date = frappe.flags.current_date
pi.insert() pi.bill_no = random_string(6)
pi.submit() pi.insert()
frappe.db.commit() pi.submit()
frappe.db.commit()
except frappe.ValidationError:
pass
if random.random() < 0.5: if random.random() < 0.5:
make_payment_entries("Sales Invoice", "Accounts Receivable") make_payment_entries("Sales Invoice", "Accounts Receivable")

View File

@@ -1,36 +1,46 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import random import random
from frappe.utils import random_string, add_days, cint import datetime
from frappe.utils import random_string, add_days, get_last_day, getdate
from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet
from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
from frappe.utils.make_random import get_random from frappe.utils.make_random import get_random
from erpnext.hr.doctype.expense_claim.expense_claim import get_expense_approver, make_bank_entry from erpnext.hr.doctype.expense_claim.expense_claim import get_expense_approver, make_bank_entry
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on, OverlapError from erpnext.hr.doctype.leave_application.leave_application import (get_leave_balance_on,
OverlapError, AttendanceAlreadyMarkedError)
def work(): def work():
frappe.set_user(frappe.db.get_global('demo_hr_user')) frappe.set_user(frappe.db.get_global('demo_hr_user'))
year, month = frappe.flags.current_date.strftime("%Y-%m").split("-") year, month = frappe.flags.current_date.strftime("%Y-%m").split("-")
prev_month = str(cint(month)- 1).zfill(2)
if month=="01":
prev_month = "12"
mark_attendance() mark_attendance()
make_leave_application() make_leave_application()
# process payroll # process payroll
if not frappe.db.get_value("Salary Slip", {"month": prev_month, "fiscal_year": year}): if not frappe.db.sql('select name from `tabSalary Slip` where month(adddate(start_date, interval 1 month))=month(curdate())'):
# process payroll for previous month
process_payroll = frappe.get_doc("Process Payroll", "Process Payroll") process_payroll = frappe.get_doc("Process Payroll", "Process Payroll")
process_payroll.company = frappe.flags.company process_payroll.company = frappe.flags.company
process_payroll.month = prev_month process_payroll.payroll_frequency = 'Monthly'
process_payroll.fiscal_year = year
process_payroll.from_date = frappe.flags.current_date # select a posting date from the previous month
process_payroll.to_date = add_days(frappe.flags.current_date, random.randint(0, 30)) process_payroll.posting_date = get_last_day(getdate(frappe.flags.current_date) - datetime.timedelta(days=10))
process_payroll.reference_number = "DemoRef23"
process_payroll.reference_date = frappe.flags.current_date
process_payroll.payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") process_payroll.payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
process_payroll.submit_salary_slip()
process_payroll.make_journal_entry() process_payroll.set_start_end_dates()
# based on frequency
process_payroll.salary_slip_based_on_timesheet = 0
process_payroll.create_salary_slips()
process_payroll.submit_salary_slips()
process_payroll.make_journal_entry(reference_date=frappe.flags.current_date,
reference_number=random_string(10))
process_payroll.salary_slip_based_on_timesheet = 1
process_payroll.create_salary_slips()
process_payroll.submit_salary_slips()
process_payroll.make_journal_entry(reference_date=frappe.flags.current_date,
reference_number=random_string(10))
if frappe.db.get_global('demo_hr_user'): if frappe.db.get_global('demo_hr_user'):
make_timesheet_records() make_timesheet_records()
@@ -101,6 +111,9 @@ def get_timesheet_based_salary_slip_employee():
where parent IN %(sal_struct)s""", {"sal_struct": sal_struct}, as_dict=True) where parent IN %(sal_struct)s""", {"sal_struct": sal_struct}, as_dict=True)
return employees return employees
else:
return []
def make_timesheet_records(): def make_timesheet_records():
employees = get_timesheet_based_salary_slip_employee() employees = get_timesheet_based_salary_slip_employee()
for e in employees: for e in employees:
@@ -159,7 +172,7 @@ def make_leave_application():
leave_application.insert() leave_application.insert()
leave_application.submit() leave_application.submit()
frappe.db.commit() frappe.db.commit()
except (OverlapError): except (OverlapError, AttendanceAlreadyMarkedError):
frappe.db.rollback() frappe.db.rollback()
def mark_attendance(): def mark_attendance():

View File

@@ -55,12 +55,13 @@ def make_opportunity():
"enquiry_from": "Customer", "enquiry_from": "Customer",
"customer": get_random("Customer"), "customer": get_random("Customer"),
"enquiry_type": "Sales", "enquiry_type": "Sales",
"with_items": 1,
"transaction_date": frappe.flags.current_date, "transaction_date": frappe.flags.current_date,
}) })
add_random_children(b, "items", rows=4, randomize = { add_random_children(b, "items", rows=4, randomize = {
"qty": (1, 5), "qty": (1, 5),
"item_code": ("Item", {"has_variants": "0", "is_fixed_asset": 0}) "item_code": ("Item", {"has_variants": 0, "is_fixed_asset": 0})
}, unique="item_code") }, unique="item_code")
b.insert() b.insert()
@@ -68,7 +69,7 @@ def make_opportunity():
def make_quotation(): def make_quotation():
# get open opportunites # get open opportunites
opportunity = get_random("Opportunity", {"status": "Open"}) opportunity = get_random("Opportunity", {"status": "Open", "with_items": 1})
if opportunity: if opportunity:
from erpnext.crm.doctype.opportunity.opportunity import make_quotation from erpnext.crm.doctype.opportunity.opportunity import make_quotation

View File

@@ -102,7 +102,7 @@ def submit_draft_stock_entries():
def make_sales_return_records(): def make_sales_return_records():
for data in frappe.get_all('Delivery Note', fields=["name"], filters={"docstatus": 1}): for data in frappe.get_all('Delivery Note', fields=["name"], filters={"docstatus": 1}):
if random.random() < 0.2: if random.random() < 0.1:
try: try:
dn = make_sales_return(data.name) dn = make_sales_return(data.name)
dn.insert() dn.insert()
@@ -113,7 +113,7 @@ def make_sales_return_records():
def make_purchase_return_records(): def make_purchase_return_records():
for data in frappe.get_all('Purchase Receipt', fields=["name"], filters={"docstatus": 1}): for data in frappe.get_all('Purchase Receipt', fields=["name"], filters={"docstatus": 1}):
if random.random() < 0.2: if random.random() < 0.1:
try: try:
pr = make_purchase_return(data.name) pr = make_purchase_return(data.name)
pr.insert() pr.insert()

View File

@@ -16,6 +16,7 @@ class LeaveDayBlockedError(frappe.ValidationError): pass
class OverlapError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass
class InvalidLeaveApproverError(frappe.ValidationError): pass class InvalidLeaveApproverError(frappe.ValidationError): pass
class LeaveApproverIdentityError(frappe.ValidationError): pass class LeaveApproverIdentityError(frappe.ValidationError): pass
class AttendanceAlreadyMarkedError(frappe.ValidationError): pass
from frappe.model.document import Document from frappe.model.document import Document
class LeaveApplication(Document): class LeaveApplication(Document):
@@ -219,7 +220,8 @@ class LeaveApplication(Document):
and docstatus = 1""", and docstatus = 1""",
(self.employee, self.from_date, self.to_date)) (self.employee, self.from_date, self.to_date))
if attendance: if attendance:
frappe.throw(_("Attendance for employee {0} is already marked for this day").format(self.employee)) frappe.throw(_("Attendance for employee {0} is already marked for this day").format(self.employee),
AttendanceAlreadyMarkedError)
def notify_employee(self, status): def notify_employee(self, status):
employee = frappe.get_doc("Employee", self.employee) employee = frappe.get_doc("Employee", self.employee)

View File

@@ -3,7 +3,10 @@
frappe.ui.form.on("Process Payroll", { frappe.ui.form.on("Process Payroll", {
onload: function(frm) { onload: function(frm) {
frm.doc.posting_date = frm.doc.start_date = frm.doc.end_date = frappe.datetime.nowdate() frm.doc.posting_date = frappe.datetime.nowdate();
frm.doc.start_date = '';
frm.doc.end_date = '';
frm.doc.payroll_frequency = '';
}, },
refresh: function(frm) { refresh: function(frm) {
@@ -32,8 +35,7 @@ frappe.ui.form.on("Process Payroll", {
method:'erpnext.hr.doctype.process_payroll.process_payroll.get_start_end_dates', method:'erpnext.hr.doctype.process_payroll.process_payroll.get_start_end_dates',
args:{ args:{
payroll_frequency: frm.doc.payroll_frequency, payroll_frequency: frm.doc.payroll_frequency,
start_date: frm.doc.start_date, start_date: frm.doc.start_date || frm.doc.posting_date
end_date: frm.doc.end_date
}, },
callback: function(r){ callback: function(r){
if (r.message){ if (r.message){
@@ -75,7 +77,7 @@ cur_frm.cscript.create_salary_slip = function(doc, cdt, cdn) {
if (r.message) if (r.message)
cur_frm.cscript.display_activity_log(r.message); cur_frm.cscript.display_activity_log(r.message);
} }
return $c('runserverobj', args={'method':'create_sal_slip','docs':doc},callback); return $c('runserverobj', args={'method':'create_salary_slips','docs':doc},callback);
} }
cur_frm.cscript.submit_salary_slip = function(doc, cdt, cdn) { cur_frm.cscript.submit_salary_slip = function(doc, cdt, cdn) {
@@ -94,7 +96,7 @@ cur_frm.cscript.submit_salary_slip = function(doc, cdt, cdn) {
cur_frm.cscript.display_activity_log(r.message); cur_frm.cscript.display_activity_log(r.message);
} }
return $c('runserverobj', args={'method':'submit_salary_slip','docs':doc},callback); return $c('runserverobj', args={'method':'submit_salary_slips','docs':doc},callback);
}); });
} }

View File

@@ -127,7 +127,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Monthly", "default": "",
"depends_on": "eval:doc.salary_slip_based_on_timesheet == 0", "depends_on": "eval:doc.salary_slip_based_on_timesheet == 0",
"fieldname": "payroll_frequency", "fieldname": "payroll_frequency",
"fieldtype": "Select", "fieldtype": "Select",
@@ -352,7 +352,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Today", "default": "",
"fieldname": "start_date", "fieldname": "start_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@@ -408,7 +408,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Today", "default": "",
"fieldname": "end_date", "fieldname": "end_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@@ -722,7 +722,7 @@
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-11-26 01:14:51.691057", "modified": "2016-12-14 01:48:22.326326",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Process Payroll", "name": "Process Payroll",

View File

@@ -5,15 +5,11 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cint, flt, nowdate, add_days, getdate from frappe.utils import cint, flt, nowdate, add_days, getdate
from frappe import _ from frappe import _
import collections
from collections import defaultdict
from calendar import monthrange
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from frappe.model.document import Document from frappe.model.document import Document
class ProcessPayroll(Document): class ProcessPayroll(Document):
def get_emp_list(self): def get_emp_list(self):
""" """
Returns list of active employees based on selected criteria Returns list of active employees based on selected criteria
@@ -23,22 +19,31 @@ class ProcessPayroll(Document):
cond += self.get_joining_releiving_condition() cond += self.get_joining_releiving_condition()
struct_cond = '' condition = ''
if self.payroll_frequency: if self.payroll_frequency:
struct_cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency} condition = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency}
sal_struct = frappe.db.sql(""" sal_struct = frappe.db.sql("""
select name from `tabSalary Structure` select
where docstatus != 2 and is_active = 'Yes' and company = %(company)s and name from `tabSalary Structure`
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s""", where
docstatus != 2 and
is_active = 'Yes'
and company = %(company)s and
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
{condition}""".format(condition=condition),
{"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) {"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
if sal_struct: if sal_struct:
cond += "and t2.parent IN %(sal_struct)s " cond += "and t2.parent IN %(sal_struct)s "
emp_list = frappe.db.sql(""" emp_list = frappe.db.sql("""
select t1.name select
from `tabEmployee` t1, `tabSalary Structure Employee` t2 t1.name
where t1.docstatus!=2 and t1.name = t2.employee from
`tabEmployee` t1, `tabSalary Structure Employee` t2
where
t1.docstatus!=2
and t1.name = t2.employee
%s """% cond, {"sal_struct": sal_struct}) %s """% cond, {"sal_struct": sal_struct})
return emp_list return emp_list
@@ -67,7 +72,7 @@ class ProcessPayroll(Document):
if not self.get(fieldname): if not self.get(fieldname):
frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname))) frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname)))
def create_sal_slip(self): def create_salary_slips(self):
""" """
Creates salary slip for selected employees if already not created Creates salary slip for selected employees if already not created
""" """
@@ -77,31 +82,26 @@ class ProcessPayroll(Document):
ss_list = [] ss_list = []
if emp_list: if emp_list:
for emp in emp_list: for emp in emp_list:
if not frappe.db.sql("""select name from `tabSalary Slip` if not frappe.db.sql("""select
where docstatus!= 2 and employee = %s and start_date >= %s and end_date <= %s and company = %s name from `tabSalary Slip`
where
docstatus!= 2 and
employee = %s and
start_date >= %s and
end_date <= %s and
company = %s
""", (emp[0], self.start_date, self.end_date, self.company)): """, (emp[0], self.start_date, self.end_date, self.company)):
if self.payroll_frequency == "Monthly" or self.payroll_frequency == '': ss = frappe.get_doc({
ss = frappe.get_doc({ "doctype": "Salary Slip",
"doctype": "Salary Slip", "salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
"salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet, "payroll_frequency": self.payroll_frequency,
"employee": emp[0], "start_date": self.start_date,
"employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"), "end_date": self.end_date,
"company": self.company, "employee": emp[0],
"posting_date": self.posting_date, "employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"),
"payroll_frequency": self.payroll_frequency "company": self.company,
}) "posting_date": self.posting_date
else: })
ss = frappe.get_doc({
"doctype": "Salary Slip",
"salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
"start_date": self.start_date,
"end_date": self.end_date,
"employee": emp[0],
"employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"),
"company": self.company,
"posting_date": self.posting_date,
"payroll_frequency": self.payroll_frequency
})
ss.insert() ss.insert()
ss_list.append(ss.name) ss_list.append(ss.name)
return self.create_log(ss_list) return self.create_log(ss_list)
@@ -129,7 +129,7 @@ class ProcessPayroll(Document):
return ss_list return ss_list
def submit_salary_slip(self): def submit_salary_slips(self):
""" """
Submit all salary slips based on selected criteria Submit all salary slips based on selected criteria
""" """
@@ -144,12 +144,11 @@ class ProcessPayroll(Document):
else: else:
try: try:
ss_obj.submit() ss_obj.submit()
except Exception,e: except frappe.ValidationError:
not_submitted_ss.append(ss[0]) not_submitted_ss.append(ss[0])
return self.create_submit_log(ss_list, not_submitted_ss) return self.create_submit_log(ss_list, not_submitted_ss)
def create_submit_log(self, all_ss, not_submitted_ss): def create_submit_log(self, all_ss, not_submitted_ss):
log = '' log = ''
if not all_ss: if not all_ss:
@@ -167,12 +166,9 @@ class ProcessPayroll(Document):
log += """ log += """
<b>Not Submitted Salary Slips: </b>\ <b>Not Submitted Salary Slips: </b>\
<br><br> %s <br><br> \ <br><br> %s <br><br> \
Reason: <br>\ Possible reasons: <br>\
May be net pay is less than 0 <br> 1. Net pay is less than 0 <br>
May be company email id specified in employee master is not valid. <br> \ 2. Company email id specified in employee master is not valid. <br> \
Please mention correct email id in employee master or if you don't want to \
send mail, uncheck 'Send Email' checkbox. <br>\
Then try to submit Salary Slip again.
"""% ('<br>'.join(not_submitted_ss)) """% ('<br>'.join(not_submitted_ss))
return log return log
@@ -287,8 +283,44 @@ class ProcessPayroll(Document):
frappe.db.set_value("Salary Slip", ss_obj.name, "status", "Paid") frappe.db.set_value("Salary Slip", ss_obj.name, "status", "Paid")
frappe.db.set_value("Salary Slip", ss_obj.name, "journal_entry", jv_name) frappe.db.set_value("Salary Slip", ss_obj.name, "journal_entry", jv_name)
def set_start_end_dates(self):
self.update(get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date))
@frappe.whitelist() @frappe.whitelist()
def get_start_end_dates(payroll_frequency, start_date=None):
'''Returns dict of start and end dates for given payroll frequency based on start_date'''
if not payroll_frequency:
frappe.throw(_("Please set Payroll Frequency first"))
if payroll_frequency == "Monthly" or payroll_frequency == "Bimonthly":
fiscal_year = get_fiscal_year(start_date)[0]
month = "%02d" % getdate(start_date).month
m = get_month_details(fiscal_year, month)
if payroll_frequency == "Bimonthly":
if getdate(start_date).day <= 15:
start_date = m['month_start_date']
end_date = m['month_mid_end_date']
else:
start_date = m['month_mid_start_date']
end_date = m['month_end_date']
else:
start_date = m['month_start_date']
end_date = m['month_end_date']
if payroll_frequency == "Weekly":
end_date = add_days(start_date, 6)
if payroll_frequency == "Fortnightly":
end_date = add_days(start_date, 13)
if payroll_frequency == "Daily":
end_date = start_date
return frappe._dict({
'start_date': start_date, 'end_date': end_date
})
def get_month_details(year, month): def get_month_details(year, month):
ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date")
if ysd: if ysd:
@@ -312,32 +344,3 @@ def get_month_details(year, month):
}) })
else: else:
frappe.throw(_("Fiscal Year {0} not found").format(year)) frappe.throw(_("Fiscal Year {0} not found").format(year))
@frappe.whitelist()
def get_start_end_dates(payroll_frequency, start_date, end_date):
if payroll_frequency == "Monthly" or payroll_frequency == "Bimonthly":
fiscal_year = get_fiscal_year(start_date)[0] or get_fiscal_year(end_date)[0]
month = "%02d" % getdate(start_date).month or "%02d" % getdate(end_date).month
m = get_month_details(fiscal_year, month)
if payroll_frequency == "Bimonthly":
if getdate(start_date).day <= 15:
start_date = m['month_start_date']
end_date = m['month_mid_end_date']
else:
start_date = m['month_mid_start_date']
end_date = m['month_end_date']
else:
start_date = m['month_start_date']
end_date = m['month_end_date']
if payroll_frequency == "Weekly":
end_date = add_days(start_date, 6)
if payroll_frequency == "Fortnightly":
end_date = add_days(start_date, 13)
if payroll_frequency == "Daily":
end_date = start_date
return frappe._dict({
'start_date': start_date, 'end_date': end_date
})

View File

@@ -26,8 +26,8 @@ class TestProcessPayroll(unittest.TestCase):
process_payroll.payment_account = payment_account process_payroll.payment_account = payment_account
process_payroll.posting_date = nowdate() process_payroll.posting_date = nowdate()
process_payroll.payroll_frequency = "Monthly" process_payroll.payroll_frequency = "Monthly"
process_payroll.create_sal_slip() process_payroll.create_salary_slips()
process_payroll.submit_salary_slip() process_payroll.submit_salary_slips()
if process_payroll.get_sal_slip_list(ss_status = 1): if process_payroll.get_sal_slip_list(ss_status = 1):
r = process_payroll.make_journal_entry(reference_number=random_string(10),reference_date=nowdate()) r = process_payroll.make_journal_entry(reference_number=random_string(10),reference_date=nowdate())

View File

@@ -44,6 +44,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Today",
"fieldname": "posting_date", "fieldname": "posting_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@@ -421,7 +422,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Today", "default": "",
"fieldname": "start_date", "fieldname": "start_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@@ -450,7 +451,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Today", "default": "",
"depends_on": "", "depends_on": "",
"fieldname": "end_date", "fieldname": "end_date",
"fieldtype": "Date", "fieldtype": "Date",
@@ -1361,7 +1362,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-12-08 12:03:31.602913", "modified": "2016-12-14 08:26:31.400930",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Slip", "name": "Salary Slip",

View File

@@ -4,18 +4,15 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import add_days, cint, cstr, flt, getdate, nowdate, rounded, date_diff, money_in_words from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
from frappe import msgprint, _ from frappe import msgprint, _
from erpnext.accounts.utils import get_fiscal_year
from erpnext.setup.utils import get_company_currency from erpnext.setup.utils import get_company_currency
from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details, get_start_end_dates from erpnext.hr.doctype.process_payroll.process_payroll import get_start_end_dates
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
from datetime import timedelta
class SalarySlip(TransactionBase): class SalarySlip(TransactionBase):
def autoname(self): def autoname(self):
self.name = make_autoname('Sal Slip/' +self.employee + '/.#####') self.name = make_autoname('Sal Slip/' +self.employee + '/.#####')
@@ -148,10 +145,10 @@ class SalarySlip(TransactionBase):
}) })
def get_date_details(self): def get_date_details(self):
date_details = get_start_end_dates(self.payroll_frequency, self.start_date, self.end_date) if not self.end_date:
self.start_date = date_details.start_date date_details = get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date)
self.end_date = date_details.end_date self.start_date = date_details.start_date
self.end_date = date_details.end_date
def check_sal_struct(self, joining_date, relieving_date): def check_sal_struct(self, joining_date, relieving_date):
cond = '' cond = ''
@@ -186,8 +183,6 @@ class SalarySlip(TransactionBase):
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(self._salary_structure_doc.salary_component) self.add_earning_for_hourly_wages(self._salary_structure_doc.salary_component)
def process_salary_structure(self): def process_salary_structure(self):
'''Calculate salary after salary structure details have been updated''' '''Calculate salary after salary structure details have been updated'''
self.get_date_details() self.get_date_details()
@@ -195,7 +190,6 @@ class SalarySlip(TransactionBase):
self.get_leave_details() self.get_leave_details()
self.calculate_net_pay() 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
for data in self.earnings: for data in self.earnings:

View File

@@ -1,3 +1,3 @@
frappe.listview_settings['Salary Slip'] = { frappe.listview_settings['Salary Slip'] = {
add_fields: ["employee", "employee_name", "fiscal_year", "month"], add_fields: ["employee", "employee_name"],
}; };

View File

@@ -86,9 +86,12 @@ class TestSalarySlip(unittest.TestCase):
date_of_joining = getdate(nowdate()) date_of_joining = getdate(nowdate())
relieving_date = getdate(nowdate()) relieving_date = getdate(nowdate())
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", date_of_joining) frappe.db.set_value("Employee", frappe.get_value("Employee",
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None) {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", date_of_joining)
frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active") 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", ss = frappe.get_doc("Salary Slip",
self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) self.make_employee_salary_slip("test_employee@salary.com", "Monthly"))
@@ -203,7 +206,6 @@ class TestSalarySlip(unittest.TestCase):
salary_slip = make_salary_slip(salary_structure, employee = employee) 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.employee_name = frappe.get_value("Employee", {"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
salary_slip.payroll_frequency = payroll_frequency salary_slip.payroll_frequency = payroll_frequency
salary_slip.start_date = nowdate()
salary_slip.posting_date = nowdate() salary_slip.posting_date = nowdate()
salary_slip.insert() salary_slip.insert()
# salary_slip.submit() # salary_slip.submit()
@@ -220,9 +222,10 @@ class TestSalarySlip(unittest.TestCase):
def get_no_of_days(self): def get_no_of_days(self):
no_of_days_in_month = calendar.monthrange(getdate(nowdate()).year, no_of_days_in_month = calendar.monthrange(getdate(nowdate()).year,
getdate(nowdate()).month) getdate(nowdate()).month)
no_of_holidays_in_month = len([1 for i in calendar.monthcalendar(getdate(nowdate()).year, no_of_holidays_in_month = len([1 for i in calendar.monthcalendar(getdate(nowdate()).year,
getdate(nowdate()).month) if i[6] != 0]) getdate(nowdate()).month) if i[6] != 0])
return [no_of_days_in_month[1], no_of_holidays_in_month] return [no_of_days_in_month[1], no_of_holidays_in_month]

View File

@@ -100,7 +100,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "Monthly",
"depends_on": "eval:(!doc.salary_slip_based_on_timesheet)", "depends_on": "eval:(!doc.salary_slip_based_on_timesheet)",
"fieldname": "payroll_frequency", "fieldname": "payroll_frequency",
"fieldtype": "Select", "fieldtype": "Select",
@@ -894,7 +894,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-12-07 14:57:22.083825", "modified": "2016-12-14 02:02:10.848614",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Structure", "name": "Salary Structure",

View File

@@ -7,15 +7,17 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import get_datetime, get_datetime_str, formatdate from frappe.utils import get_datetime_str, formatdate, nowdate
class CurrencyExchange(Document): class CurrencyExchange(Document):
def autoname(self): def autoname(self):
self.name = formatdate(get_datetime_str(self.date),"yyyy-MM-dd") + "-" + self.from_currency + "-" + self.to_currency if not self.date:
#self.name = self.date + "-" + self.from_currency + "-" + self.to_currency self.date = nowdate()
self.name = '{0}-{1}-{2}'.format(formatdate(get_datetime_str(self.date), "yyyy-MM-dd"),
self.from_currency, self.to_currency)
def validate(self): def validate(self):
self.validate_value("exchange_rate", ">", 0) self.validate_value("exchange_rate", ">", 0)
if self.from_currency == self.to_currency: if self.from_currency == self.to_currency:
frappe.throw(_("From Currency and To Currency cannot be same")) frappe.throw(_("From Currency and To Currency cannot be same"))

View File

@@ -31,8 +31,8 @@ def install(country=None):
'is_group': 0, 'parent_item_group': _('All Item Groups') }, 'is_group': 0, 'parent_item_group': _('All Item Groups') },
# salary component # salary component
{'doctype': 'Salary Component', 'salary_component': _('Income Tax'), 'description': _('Income Tax')}, {'doctype': 'Salary Component', 'salary_component': _('Income Tax'), 'description': _('Income Tax'), 'type': 'Deduction'},
{'doctype': 'Salary Component', 'salary_component': _('Basic'), 'description': _('Basic')}, {'doctype': 'Salary Component', 'salary_component': _('Basic'), 'description': _('Basic'), 'type': 'Earning'},
# expense claim type # expense claim type
{'doctype': 'Expense Claim Type', 'name': _('Calls'), 'expense_type': _('Calls')}, {'doctype': 'Expense Claim Type', 'name': _('Calls'), 'expense_type': _('Calls')},
@@ -186,9 +186,6 @@ def install(country=None):
{'doctype': "Print Heading", 'print_heading': _("Credit Note")}, {'doctype': "Print Heading", 'print_heading': _("Credit Note")},
{'doctype': "Print Heading", 'print_heading': _("Debit Note")}, {'doctype': "Print Heading", 'print_heading': _("Debit Note")},
{"doctype": "Salary Component", "salary_component": _("Basic")},
{"doctype": "Salary Component", "salary_component": _("Income Tax")},
] ]
from erpnext.setup.setup_wizard.industry_type import get_industry_types from erpnext.setup.setup_wizard.industry_type import get_industry_types