feat: Validation, sider and form dashbord

This commit is contained in:
Anurag Mishra
2021-06-15 16:31:49 +05:30
parent da2e95dbcc
commit 769d774ccc
19 changed files with 218 additions and 102 deletions

View File

@@ -45,15 +45,21 @@ frappe.ui.form.on('Attendance', {
},
set_overtime_type: function(frm) {
frappe.call({
method: "erpnext.hr.doctype.attendance.attendance.get_overtime_type",
args: {
employee: frm.doc.employee,
},
callback: function(r) {
if (r.message) {
frm.set_value("overtime_type", r.message);
}
frappe.db.get_single_value("Payroll Settings", "overtime_based_on").then((r)=>{
if (r == "Attendance") {
frappe.call({
method: "erpnext.hr.doctype.attendance.attendance.get_overtime_type",
args: {
employee: frm.doc.employee,
},
callback: function(r) {
if (r.message) {
frm.set_value("overtime_type", r.message);
}
}
});
} else {
frm.set_value("overtime_type", '');
}
});
},

View File

@@ -4,11 +4,10 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate, nowdate
from frappe import _
from frappe.model.document import Document
from erpnext.hr.utils import validate_active_employee
from frappe.utils import cstr, get_datetime, formatdate, getdate
from frappe.utils import cstr, get_datetime, formatdate, getdate, nowdate
class Attendance(Document):
def validate(self):
@@ -58,7 +57,7 @@ class Attendance(Document):
self.overtime_type = get_overtime_type(self.employee)
if self.overtime_type:
if frappe.db.get_single_value("Payroll Settings", "overtime_based_on") != "Attendance":
if frappe.db.get_single_value("Payroll Settings", "overtime_based_on") != "Attendance":
frappe.msgprint(_('Set "Calculate Overtime Based On Attendance" to Attendance for Overtime Slip Creation'))
maximum_overtime_hours_allowed = frappe.db.get_single_value("Payroll Settings", "maximum_overtime_hours_allowed")
@@ -136,25 +135,28 @@ def get_shift_type(employee, attendance_date):
@frappe.whitelist()
def get_overtime_type(employee):
emp_department = frappe.db.get_value("Employee", employee, "department")
if emp_department:
overtime_type = frappe.get_list("Overtime Type", filters={"party_type": "Department", "party": emp_department}, fields=['name'])
if len(overtime_type):
overtime_type = overtime_type[0].name
emp_grade = frappe.db.get_value("Employee", employee, "grade")
if emp_grade:
overtime_type = frappe.get_list("Overtime Type", filters={"party_type": "Employee Grade", "party": emp_grade},
fields=['name'])
if len(overtime_type):
overtime_type = overtime_type[0].name
overtime_type = frappe.get_list("Overtime Type", filters={"party_type": "Employee", "party": employee}, fields=['name'])
emp_department = frappe.db.get_value("Employee", employee, "department")
if emp_department:
overtime_type = frappe.get_list("Overtime Type", filters={
"applicable_for": "Department", "department": emp_department}, fields=['name'])
if len(overtime_type):
overtime_type = overtime_type[0].name
return overtime_type
emp_grade = frappe.db.get_value("Employee", employee, "grade")
if emp_grade:
overtime_type = frappe.get_list("Overtime Type", filters={
"applicable_for": "Employee Grade", "employee_grade": emp_grade},
fields=['name'])
if len(overtime_type):
overtime_type = overtime_type[0].name
overtime_type = frappe.get_list("Overtime Type", filters={
"applicable_for": "Employee", "employee": employee}, fields=['name'])
if len(overtime_type):
overtime_type = overtime_type[0].name
return overtime_type
@frappe.whitelist()
def get_events(start, end, filters=None):

View File

@@ -42,11 +42,11 @@ class EmployeeCheckin(Document):
self.shift_start = shift_actual_timings[2].start_datetime
self.shift_end = shift_actual_timings[2].end_datetime
elif frappe.db.get_value("Shift Type", shift_actual_timings[2].shift_type.name, "allow_overtime"):
#because after Actual time it takes check-in/out invalid
#if employee checkout late or check-in before before shift timing adding time buffer.
self.shift = shift_actual_timings[2].shift_type.name
self.shift_start = shift_actual_timings[2].start_datetime
self.shift_end = shift_actual_timings[2].end_datetime
#because after Actual time it takes check-in/out invalid
#if employee checkout late or check-in before before shift timing adding time buffer.
self.shift = shift_actual_timings[2].shift_type.name
self.shift_start = shift_actual_timings[2].start_datetime
self.shift_end = shift_actual_timings[2].end_datetime
@frappe.whitelist()
def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=None, log_type=None, skip_auto_attendance=0, employee_fieldname='attendance_device_id'):

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint, cstr, getdate, now_datetime, nowdate
from frappe.utils import cstr, getdate, now_datetime, nowdate
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
from erpnext.hr.utils import validate_active_employee
@@ -61,12 +61,12 @@ class ShiftAssignment(Document):
def throw_overlap_error(self, shift_details):
shift_details = frappe._dict(shift_details)
if shift_details.docstatus == 1 and shift_details.status == "Active":
msg = _("Employee {0} already has Active Shift {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name))
msg = _("Employee {0} already has Active Shift {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)) + " "
if shift_details.start_date:
msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y"))
msg += _("from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y")) + " "
title = "Ongoing Shift"
if shift_details.end_date:
msg += _(" to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y"))
msg += _("to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y"))
title = "Active Shift"
if msg:
frappe.throw(msg, title=title)
@@ -273,7 +273,7 @@ def get_actual_start_end_datetime_of_shift(employee, for_datetime, consider_defa
if timestamp and for_datetime <= timestamp:
timestamp_index = index
break
if timestamp_index and timestamp_index%2 == 1:
if timestamp_index and timestamp_index % 2 == 1:
shift_details = shift_timings_as_per_timestamp[int((timestamp_index-1)/2)]
actual_shift_start = shift_details.actual_start
actual_shift_end = shift_details.actual_end

View File

@@ -25,7 +25,7 @@
"column_break_19",
"process_attendance_after",
"last_sync_of_checkin",
"grace_period_settings_auto_attendance_section",
"grace_period_settings_section",
"enable_entry_grace_period",
"late_entry_grace_period",
"column_break_18",
@@ -141,12 +141,6 @@
"fieldtype": "Section Break",
"label": "Auto Attendance Settings"
},
{
"depends_on": "enable_auto_attendance",
"fieldname": "grace_period_settings_auto_attendance_section",
"fieldtype": "Section Break",
"label": "Grace Period Settings For Auto Attendance"
},
{
"default": "0",
"description": "Mark attendance based on 'Employee Checkin' for Employees assigned to this shift.",
@@ -188,10 +182,16 @@
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
},
{
"depends_on": "enable_auto_attendance",
"fieldname": "grace_period_settings_section",
"fieldtype": "Section Break",
"label": "Grace Period Settings"
}
],
"links": [],
"modified": "2021-06-09 13:38:25.697100",
"modified": "2021-06-15 15:22:55.756078",
"modified_by": "Administrator",
"module": "HR",
"name": "Shift Type",

View File

@@ -4,11 +4,8 @@
from __future__ import unicode_literals
import itertools
from datetime import timedelta
import frappe
from frappe import _
from math import modf
from frappe.model.document import Document
from frappe.utils import cint, getdate, get_datetime
from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift, get_employee_shift
@@ -26,7 +23,7 @@ class ShiftType(Document):
end_time = self.end_time.split(":")
start_time = self.start_time.split(":")
shift_end = timedelta(hours = int(end_time[0]), minutes = int(end_time[1]), seconds = int(end_time[2]))
shift_end = timedelta(hours = int(end_time[0]), minutes = int(end_time[1]), seconds = int(end_time[2]))
shift_start = timedelta(hours =int(start_time[0]), minutes = int(start_time[1]), seconds = int(start_time[2]))
if shift_end > shift_start:

View File

@@ -10,7 +10,10 @@ def get_data():
},
'transactions': [
{
'items': ['Attendance', 'Employee Checkin', 'Shift Request', 'Shift Assignment']
'items': ['Attendance', 'Employee Checkin']
},
{
'items': ['Shift Request', 'Shift Assignment']
}
]
}

View File

@@ -235,7 +235,7 @@ def get_gratuity_rule_slabs(gratuity_rule):
return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"], order_by="idx")
def get_salary_structure(employee):
salary_structure_assignment = frappe.get_list("Salary Structure Assignment", filters = {
salary_structure_assignment = frappe.get_list("Salary Structure Assignment", filters = {
"employee": employee, 'docstatus': 1
},
fields=["from_date", "salary_structure"],

View File

@@ -18,6 +18,7 @@
"to_date",
"column_break_10",
"payroll_frequency",
"salary_slip",
"section_break_12",
"overtime_details",
"section_break_13",
@@ -134,12 +135,20 @@
"fieldtype": "Date",
"label": "Posting Date",
"reqd": 1
},
{
"allow_on_submit": 1,
"fieldname": "salary_slip",
"fieldtype": "Link",
"label": "Salary Slip",
"options": "Salary Slip",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-06-10 13:35:57.511257",
"modified": "2021-06-15 12:53:17.355755",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Overtime Slip",

View File

@@ -1,14 +1,43 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from erpnext.hr.doctype.attendance.attendance import get_overtime_type
import frappe
from frappe import _
from frappe.utils import get_datetime, getdate
from frappe import _, bold
from frappe.utils import get_datetime, getdate, get_link_to_form, formatdate
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates
from erpnext.payroll.doctype.gratuity.gratuity import get_salary_structure
from frappe.model.document import Document
class OvertimeSlip(Document):
def validate(self):
self.validate_overlap()
if self.from_date >= self.to_date:
frappe.throw(_("From date can not be greater than To date"))
if not (self.from_date or self.to_date or self.payroll_frequency):
date = self.from_date or self.posting_date
get_frequency_and_dates(self.employee, date)
if not len(self.overtime_details):
self.get_emp_and_overtime_details()
def validate_overlap(self):
if not self.name:
# hack! if name is null, it could cause problems with !=
self.name = "new-overtime-slip-1"
overtime_slips = frappe.db.get_all("Overtime Slip", filters = {
"docstatus": ("<", 2),
"employee": self.employee,
"to_date": (">=", self.from_date),
"from_date": ("<=", self.to_date)
})
if len(overtime_slips):
form_link = get_link_to_form("Overtime Slip", overtime_slips[0].name)
msg = _("Overtime Slip:{0} has been created between {1} and {1}").format(
bold(form_link),
bold(formatdate(self.from_date)), bold(formatdate(self.to_date)))
frappe.throw(msg)
def on_submit(self):
if self.status == "Pending":
frappe.throw(_("Overtime Slip with Status 'Approved' or 'Rejected' are allowed for Submission"))

View File

@@ -3,11 +3,11 @@
frappe.ui.form.on('Overtime Type', {
setup: function(frm) {
frm.set_query("party_type", () => {
let party_type = ["Employee", "Department", "Employee Grade"];
frm.set_query("applicable_for", () => {
let doctype_list = ["Employee", "Department", "Employee Grade"];
return {
filters: {
name: ["in", party_type]
name: ["in", doctype_list]
}
};
});

View File

@@ -6,8 +6,10 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"party_type",
"party",
"applicable_for",
"employee",
"department",
"employee_grade",
"column_break_3",
"applicable_salary_component",
"pay_rate_multipliers_section",
@@ -19,20 +21,6 @@
"public_holiday_multiplier"
],
"fields": [
{
"fieldname": "party_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Party Type",
"options": "DocType",
"reqd": 1
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"label": "Party",
"options": "party_type"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
@@ -41,7 +29,8 @@
"fieldname": "applicable_salary_component",
"fieldtype": "Table MultiSelect",
"label": "Applicable Salary Component",
"options": "Overtime Salary Component"
"options": "Overtime Salary Component",
"reqd": 1
},
{
"description": "Pay Rate Multipliers apply to the hourly wage for the position you\u2019re working during the overtime hours.",
@@ -87,11 +76,43 @@
"fieldtype": "Float",
"label": "Public Holiday Multiplier",
"mandatory_depends_on": "eval: doc.applicable_for_public_holiday == 1"
},
{
"fieldname": "applicable_for",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Applicable For",
"options": "DocType",
"reqd": 1
},
{
"depends_on": "eval: doc.applicable_for == \"Employee\"",
"fieldname": "employee",
"fieldtype": "Link",
"label": "Employee",
"mandatory_depends_on": "eval: doc.applicable_for == \"Employee\"",
"options": "Employee"
},
{
"depends_on": "eval: doc.applicable_for == \"Department\"",
"fieldname": "department",
"fieldtype": "Link",
"label": "Department",
"mandatory_depends_on": "eval: doc.applicable_for == \"Department\"",
"options": "Department"
},
{
"depends_on": "eval: doc.applicable_for == \"Employee Grade\"",
"fieldname": "employee_grade",
"fieldtype": "Link",
"label": "Employee Grade",
"mandatory_depends_on": "eval: doc.applicable_for == \"Employee Grade\"",
"options": "Employee Grade"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-06-09 15:43:43.891270",
"modified": "2021-06-15 13:49:20.612464",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Overtime Type",

View File

@@ -0,0 +1,15 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'overtime_type',
'transactions': [
{
'items': ['Attendance', 'Timesheet']
},
{
'items': ['Overtime Slip']
}
]
}

View File

@@ -20,10 +20,9 @@
"section_break_12",
"overtime_based_on",
"maximum_overtime_hours_allowed",
"overtime_salary_component",
"column_break_14",
"fetch_standard_working_hours_from_shift_type",
"is_overtime_approval_required"
"overtime_salary_component",
"fetch_standard_working_hours_from_shift_type"
],
"fields": [
{
@@ -112,6 +111,7 @@
},
{
"default": "0",
"depends_on": "eval: doc.overtime_based_on == \"Attendance\"",
"description": "If unchecked, Standard Working Hours as defined in HR Settings will be taken into consideration.",
"fieldname": "fetch_standard_working_hours_from_shift_type",
"fieldtype": "Check",
@@ -128,12 +128,6 @@
"label": "Calculate Overtime Hours Based On",
"options": "Attendance\nTimesheet"
},
{
"default": "0",
"fieldname": "is_overtime_approval_required",
"fieldtype": "Check",
"label": "Is Overtime Approval Required"
},
{
"description": "Overtime payment will not be given for more than the defined hours limit.",
"fieldname": "maximum_overtime_hours_allowed",
@@ -145,7 +139,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-05-26 15:56:25.313007",
"modified": "2021-06-15 13:55:44.563796",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll Settings",

View File

@@ -423,7 +423,7 @@
{
"fieldname": "net_pay_info",
"fieldtype": "Section Break",
"label": "net pay info"
"label": "Net Pay Info"
},
{
"fieldname": "net_pay",
@@ -631,7 +631,7 @@
"idx": 9,
"is_submittable": 1,
"links": [],
"modified": "2021-03-31 22:44:09.772331",
"modified": "2021-06-15 12:32:01.369615",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Slip",

View File

@@ -82,8 +82,23 @@ class SalarySlip(TransactionBase):
if (frappe.db.get_single_value("Payroll Settings", "email_salary_slip_to_employee")) and not frappe.flags.via_payroll_entry:
self.email_salary_slip()
self.update_overtime_slip()
self.update_payment_status_for_gratuity()
def update_overtime_slip(self):
overtime_slips = []
for data in self.earnings:
if data.overtime_slips:
overtime_slips.extend(data.overtime_slips.split(", "))
if self.docstatus == 1:
for slip in overtime_slips:
frappe.db.set_value("Overtime Slip", slip, "salary_slip", self.name)
if self.docstatus == 2:
for slip in overtime_slips:
frappe.db.set_value("Overtime Slip", slip, "salary_slip", None)
def update_payment_status_for_gratuity(self):
add_salary = frappe.db.get_all("Additional Salary",
filters = {
@@ -101,6 +116,7 @@ class SalarySlip(TransactionBase):
def on_cancel(self):
self.set_status()
self.update_status()
self.update_overtime_slip
self.update_payment_status_for_gratuity()
self.cancel_loan_repayment_entry()
@@ -513,7 +529,7 @@ class SalarySlip(TransactionBase):
def process_overtime_slips(self):
overtime_slips = self.get_overtime_slips()
amounts, processed_overtime_slips = self.get_overtime_amount(overtime_slips)
amounts, processed_overtime_slips = self.get_overtime_amount(overtime_slips)
self.add_overtime_component(amounts, processed_overtime_slips)
def get_overtime_slips(self):
@@ -521,11 +537,14 @@ class SalarySlip(TransactionBase):
'employee': self.employee,
'posting_date': (">=", self.start_date),
'posting_date': ("<=", self.end_date),
'salary_slip': '',
'docstatus': 1
}, fields = ["name", "from_date", 'to_date'])
def get_overtime_amount(self, overtime_slips):
standard_duration_amount = 0; weekends_duration_amount= 0; public_holidays_duration_amount = 0
standard_duration_amount = 0
weekends_duration_amount= 0
public_holidays_duration_amount = 0
calculated_amount = 0
processed_overtime_slips = []
overtime_types_details = {}
@@ -541,7 +560,7 @@ class SalarySlip(TransactionBase):
for detail in details:
overtime_hours = detail.overtime_duration / 3600
if not detail.overtime_type in overtime_types_details:
if detail.overtime_type not in overtime_types_details:
details, applicable_components = self.get_overtime_type_detail(detail.overtime_type)
overtime_types_details[detail.overtime_type] = details
if len(applicable_components):
@@ -551,24 +570,28 @@ class SalarySlip(TransactionBase):
frappe.bold(detail.overtime_type)))
if "applicable_amount" not in overtime_types_details[detail.overtime_type].keys():
component_amount = sum([data.default_amount for data in self.earnings \
if data.salary_component in overtime_types_details[detail.overtime_type]["components"] \
and not data.get('additional_salary', None)])
component_amount = sum([data.default_amount for data in self.earnings if data.salary_component in \
overtime_types_details[detail.overtime_type]["components"] and not data.get('additional_salary', None)])
overtime_types_details[detail.overtime_type]["applicable_daily_amount"] = component_amount/self.total_working_days
standard_working_hours = detail.standard_working_time/3600
applicable_hourly_wages = overtime_types_details[detail.overtime_type]["applicable_daily_amount"]/standard_working_hours
weekend_multiplier = overtime_types_details[detail.overtime_type]['standard_multiplier']
public_holiday_multiplier = overtime_types_details[detail.overtime_type]['standard_multiplier']
if overtime_types_details[detail.overtime_type]['applicable_for_weekend']:
weekend_multiplier = overtime_types_details[detail.overtime_type]['weekend_multiplier']
if overtime_types_details[detail.overtime_type]['applicable_for_public_holiday']:
public_holiday_multiplier = overtime_types_details[detail.overtime_type]['public_holiday_multiplier']
overtime_date = cstr(detail.date)
if overtime_date in holiday_date_map.keys():
if holiday_date_map[overtime_date].weekly_off == 1:
calculated_amount = overtime_hours * applicable_hourly_wages *\
overtime_types_details[detail.overtime_type]['weekend_multiplier']
calculated_amount = overtime_hours * applicable_hourly_wages * weekend_multiplier
weekends_duration_amount += calculated_amount
elif holiday_date_map[overtime_date].weekly_off == 0:
calculated_amount = overtime_hours * applicable_hourly_wages *\
overtime_types_details[detail.overtime_type]['public_holiday_multiplier']
calculated_amount = overtime_hours * applicable_hourly_wages * public_holiday_multiplier
public_holidays_duration_amount += calculated_amount
else:
calculated_amount = overtime_hours * applicable_hourly_wages *\
@@ -578,6 +601,7 @@ class SalarySlip(TransactionBase):
processed_overtime_slips.append(slip.name)
return [weekends_duration_amount, public_holidays_duration_amount, standard_duration_amount] , processed_overtime_slips
def add_overtime_component(self, amounts, processed_overtime_slips):
if len(amounts):
overtime_salary_component = frappe.db.get_single_value("Payroll Settings", "overtime_salary_component")
@@ -602,7 +626,11 @@ class SalarySlip(TransactionBase):
)
def get_overtime_type_detail(self, name):
detail = frappe.get_all("Overtime Type", filters = {"name": name}, fields = ["name", "standard_multiplier", "weekend_multiplier", "public_holiday_multiplier"])[0]
detail = frappe.get_all("Overtime Type",
filters = {"name": name},
fields = ["name", "standard_multiplier", "weekend_multiplier", "public_holiday_multiplier",
"applicable_for_weekend", "applicable_for_public_holiday"]
)[0]
components = frappe.get_all("Overtime Salary Component",
filters = {"parent": name}, fields = ["salary_component"])

View File

@@ -0,0 +1,12 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'salary_slip',
'transactions': [
{
'items': ['Overtime Slip']
}
]
}

View File

@@ -67,7 +67,7 @@ def set_default_settings(args):
payroll_settings = frappe.get_doc("Payroll Settings")
payroll_settings.overtime_based_on = "Attendance"
Payroll_settings.overtime_salary_component = _("Overtime Allowance")
payroll_settings.overtime_salary_component = _("Overtime Allowance")
payroll_settings.save()
def set_no_copy_fields_in_variant_settings():

View File

@@ -320,7 +320,7 @@ def update_hr_defaults():
def update_payroll_defaults():
payroll_settings = frappe.get_doc("Payroll Settings")
payroll_settings.overtime_based_on = "Attendance"
Payroll_settings.overtime_salary_component = _("Overtime Allowance")
payroll_settings.overtime_salary_component = _("Overtime Allowance")
payroll_settings.save()