Merge branch 'version-13-beta-pre-release' of https://github.com/frappe/erpnext into enconnex_erpnext

This commit is contained in:
Deepesh Garg
2020-12-28 17:04:35 +05:30
555 changed files with 33867 additions and 10875 deletions

View File

@@ -20,7 +20,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
approvers = []
department_details = {}
department_list = []
employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True)
employee = frappe.get_value("Employee", filters.get("employee"), ["employee_name","department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True)
employee_department = filters.get("department") or employee.department
if employee_department:
@@ -59,11 +59,9 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
if len(approvers) == 0:
frappe.throw(_("Please set {0} for the Employee or for Department: {1}").
format(
field_name, frappe.bold(employee_department),
frappe.bold(employee.name)
),
title=_(field_name + " Missing"))
error_msg = _("Please set {0} for the Employee: {1}").format(field_name, frappe.bold(employee.employee_name))
if department_list:
error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department))
frappe.throw(error_msg, title=_(field_name + " Missing"))
return set(tuple(approver) for approver in approvers)

View File

@@ -57,7 +57,6 @@
"column_break_45",
"shift_request_approver",
"attendance_and_leave_details",
"leave_policy",
"attendance_device_id",
"column_break_44",
"holiday_list",
@@ -411,14 +410,6 @@
"oldfieldtype": "Link",
"options": "Branch"
},
{
"fetch_from": "grade.default_leave_policy",
"fetch_if_empty": 1,
"fieldname": "leave_policy",
"fieldtype": "Link",
"label": "Leave Policy",
"options": "Leave Policy"
},
{
"description": "Applicable Holiday List",
"fieldname": "holiday_list",
@@ -672,10 +663,10 @@
"oldfieldtype": "Date"
},
{
"depends_on": "eval:doc.status == \"Left\"",
"fieldname": "relieving_date",
"fieldtype": "Date",
"label": "Relieving Date",
"mandatory_depends_on": "eval:doc.status == \"Left\"",
"oldfieldname": "relieving_date",
"oldfieldtype": "Date"
},
@@ -822,7 +813,7 @@
"idx": 24,
"image_field": "image",
"links": [],
"modified": "2020-10-06 15:58:23.805489",
"modified": "2020-10-16 15:02:04.283657",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",

View File

@@ -15,11 +15,16 @@ frappe.ui.form.on('Employee Advance', {
});
frm.set_query("advance_account", function() {
if (!frm.doc.employee) {
frappe.msgprint(__("Please select employee first"));
}
var company_currency = erpnext.get_currency(frm.doc.company);
return {
filters: {
"root_type": "Asset",
"is_group": 0,
"company": frm.doc.company
"company": frm.doc.company,
"account_currency": ["in", [frm.doc.currency, company_currency]],
}
};
});
@@ -63,7 +68,7 @@ frappe.ui.form.on('Employee Advance', {
}, __('Create'));
}else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
frm.add_custom_button(__("Deduction from salary"), function() {
frm.events.make_deduction_via_additional_salary(frm)
frm.events.make_deduction_via_additional_salary(frm);
}, __('Create'));
}
}
@@ -127,7 +132,9 @@ frappe.ui.form.on('Employee Advance', {
'employee_advance_name': frm.doc.name,
'return_amount': flt(frm.doc.paid_amount - frm.doc.claimed_amount),
'advance_account': frm.doc.advance_account,
'mode_of_payment': frm.doc.mode_of_payment
'mode_of_payment': frm.doc.mode_of_payment,
'currency': frm.doc.currency,
'exchange_rate': frm.doc.exchange_rate
},
callback: function(r) {
const doclist = frappe.model.sync(r.message);
@@ -138,16 +145,72 @@ frappe.ui.form.on('Employee Advance', {
employee: function (frm) {
if (frm.doc.employee) {
return frappe.call({
method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
args: {
"employee": frm.doc.employee,
"posting_date": frm.doc.posting_date
},
callback: function(r) {
frm.set_value("pending_amount",r.message);
}
});
frappe.run_serially([
() => frm.trigger('get_employee_currency'),
() => frm.trigger('get_pending_amount')
]);
}
},
get_pending_amount: function(frm) {
frappe.call({
method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
args: {
"employee": frm.doc.employee,
"posting_date": frm.doc.posting_date
},
callback: function(r) {
frm.set_value("pending_amount", r.message);
}
});
},
get_employee_currency: function(frm) {
frappe.call({
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
args: {
employee: frm.doc.employee,
},
callback: function(r) {
if (r.message) {
frm.set_value('currency', r.message);
frm.refresh_fields();
}
}
});
},
currency: function(frm) {
var from_currency = frm.doc.currency;
var company_currency;
if (!frm.doc.company) {
company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
} else {
company_currency = erpnext.get_currency(frm.doc.company);
}
if (from_currency != company_currency) {
frm.events.set_exchange_rate(frm, from_currency, company_currency);
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" );
}
frm.refresh_fields();
},
set_exchange_rate: function(frm, from_currency, company_currency) {
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
from_currency: from_currency,
to_currency: company_currency,
},
callback: function(r) {
frm.set_value("exchange_rate", flt(r.message));
frm.set_df_property('exchange_rate', 'hidden', 0);
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
+ " = [?] " + company_currency);
}
});
}
});

View File

@@ -13,6 +13,8 @@
"department",
"column_break_4",
"posting_date",
"currency",
"exchange_rate",
"repay_unclaimed_amount_from_salary",
"section_break_8",
"purpose",
@@ -91,7 +93,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Advance Amount",
"options": "Company:company:default_currency",
"options": "currency",
"reqd": 1
},
{
@@ -99,7 +101,7 @@
"fieldtype": "Currency",
"label": "Paid Amount",
"no_copy": 1,
"options": "Company:company:default_currency",
"options": "currency",
"read_only": 1
},
{
@@ -107,7 +109,7 @@
"fieldtype": "Currency",
"label": "Claimed Amount",
"no_copy": 1,
"options": "Company:company:default_currency",
"options": "currency",
"read_only": 1
},
{
@@ -161,7 +163,7 @@
"fieldname": "return_amount",
"fieldtype": "Currency",
"label": "Returned Amount",
"options": "Company:company:default_currency",
"options": "currency",
"read_only": 1
},
{
@@ -175,13 +177,31 @@
"fieldname": "pending_amount",
"fieldtype": "Currency",
"label": "Pending Amount",
"options": "Company:company:default_currency",
"options": "currency",
"read_only": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency",
"reqd": 1
},
{
"depends_on": "currency",
"fieldname": "exchange_rate",
"fieldtype": "Float",
"label": "Exchange Rate",
"precision": "9",
"print_hide": 1,
"reqd": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-06-12 12:42:39.833818",
"modified": "2020-11-25 12:01:55.980721",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",

View File

@@ -19,7 +19,6 @@ class EmployeeAdvance(Document):
def validate(self):
self.set_status()
self.validate_employee_advance_account()
def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry')
@@ -38,16 +37,9 @@ class EmployeeAdvance(Document):
elif self.docstatus == 2:
self.status = "Cancelled"
def validate_employee_advance_account(self):
company_currency = erpnext.get_company_currency(self.company)
if (self.advance_account and
company_currency != frappe.db.get_value('Account', self.advance_account, 'account_currency')):
frappe.throw(_("Advance account currency should be same as company currency {0}")
.format(company_currency))
def set_total_advance_paid(self):
paid_amount = frappe.db.sql("""
select ifnull(sum(debit_in_account_currency), 0) as paid_amount
select ifnull(sum(debit), 0) as paid_amount
from `tabGL Entry`
where against_voucher_type = 'Employee Advance'
and against_voucher = %s
@@ -56,7 +48,7 @@ class EmployeeAdvance(Document):
""", (self.name, self.employee), as_dict=1)[0].paid_amount
return_amount = frappe.db.sql("""
select name, ifnull(sum(credit_in_account_currency), 0) as return_amount
select ifnull(sum(credit), 0) as return_amount
from `tabGL Entry`
where against_voucher_type = 'Employee Advance'
and voucher_type != 'Expense Claim'
@@ -65,6 +57,11 @@ class EmployeeAdvance(Document):
and party = %s
""", (self.name, self.employee), as_dict=1)[0].return_amount
if paid_amount != 0:
paid_amount = flt(paid_amount) / flt(self.exchange_rate)
if return_amount != 0:
return_amount = flt(return_amount) / flt(self.exchange_rate)
if flt(paid_amount) > self.advance_amount:
frappe.throw(_("Row {0}# Paid Amount cannot be greater than requested advance amount"),
EmployeeAdvanceOverPayment)
@@ -107,16 +104,27 @@ def make_bank_entry(dt, dn):
doc = frappe.get_doc(dt, dn)
payment_account = get_default_bank_cash_account(doc.company, account_type="Cash",
mode_of_payment=doc.mode_of_payment)
if not payment_account:
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
advance_account_currency = frappe.db.get_value('Account', doc.advance_account, 'account_currency')
advance_amount, advance_exchange_rate = get_advance_amount_advance_exchange_rate(advance_account_currency,doc )
paying_amount, paying_exchange_rate = get_paying_amount_paying_exchange_rate(payment_account, doc)
je = frappe.new_doc("Journal Entry")
je.posting_date = nowdate()
je.voucher_type = 'Bank Entry'
je.company = doc.company
je.remark = 'Payment against Employee Advance: ' + dn + '\n' + doc.purpose
je.multi_currency = 1 if advance_account_currency != payment_account.account_currency else 0
je.append("accounts", {
"account": doc.advance_account,
"debit_in_account_currency": flt(doc.advance_amount),
"account_currency": advance_account_currency,
"exchange_rate": flt(advance_exchange_rate),
"debit_in_account_currency": flt(advance_amount),
"reference_type": "Employee Advance",
"reference_name": doc.name,
"party_type": "Employee",
@@ -128,19 +136,41 @@ def make_bank_entry(dt, dn):
je.append("accounts", {
"account": payment_account.account,
"cost_center": erpnext.get_default_cost_center(doc.company),
"credit_in_account_currency": flt(doc.advance_amount),
"credit_in_account_currency": flt(paying_amount),
"account_currency": payment_account.account_currency,
"account_type": payment_account.account_type
"account_type": payment_account.account_type,
"exchange_rate": flt(paying_exchange_rate)
})
return je.as_dict()
def get_advance_amount_advance_exchange_rate(advance_account_currency, doc):
if advance_account_currency != doc.currency:
advance_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
advance_exchange_rate = 1
else:
advance_amount = doc.advance_amount
advance_exchange_rate = doc.exchange_rate
return advance_amount, advance_exchange_rate
def get_paying_amount_paying_exchange_rate(payment_account, doc):
if payment_account.account_currency != doc.currency:
paying_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
paying_exchange_rate = 1
else:
paying_amount = doc.advance_amount
paying_exchange_rate = doc.exchange_rate
return paying_amount, paying_exchange_rate
@frappe.whitelist()
def create_return_through_additional_salary(doc):
import json
doc = frappe._dict(json.loads(doc))
additional_salary = frappe.new_doc('Additional Salary')
additional_salary.employee = doc.employee
additional_salary.currency = doc.currency
additional_salary.amount = doc.paid_amount - doc.claimed_amount
additional_salary.company = doc.company
additional_salary.ref_doctype = doc.doctype
@@ -149,26 +179,28 @@ def create_return_through_additional_salary(doc):
return additional_salary
@frappe.whitelist()
def make_return_entry(employee, company, employee_advance_name, return_amount, advance_account, mode_of_payment=None):
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
mode_of_payment_type = ''
if mode_of_payment:
mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
if mode_of_payment_type not in ["Cash", "Bank"]:
# if mode of payment is General then it unset the type
mode_of_payment_type = None
def make_return_entry(employee, company, employee_advance_name, return_amount, advance_account, currency, exchange_rate, mode_of_payment=None):
bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
if not bank_cash_account:
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
je = frappe.new_doc('Journal Entry')
je.posting_date = nowdate()
# if mode of payment is Bank then voucher type is Bank Entry
je.voucher_type = '{} Entry'.format(mode_of_payment_type) if mode_of_payment_type else 'Cash Entry'
je.voucher_type = get_voucher_type(mode_of_payment)
je.company = company
je.remark = 'Return against Employee Advance: ' + employee_advance_name
je.multi_currency = 1 if advance_account_currency != bank_cash_account.account_currency else 0
advance_account_amount = flt(return_amount) if advance_account_currency==currency \
else flt(return_amount) * flt(exchange_rate)
je.append('accounts', {
'account': advance_account,
'credit_in_account_currency': return_amount,
'credit_in_account_currency': advance_account_amount,
'account_currency': advance_account_currency,
'exchange_rate': flt(exchange_rate) if advance_account_currency == currency else 1,
'reference_type': 'Employee Advance',
'reference_name': employee_advance_name,
'party_type': 'Employee',
@@ -176,13 +208,25 @@ def make_return_entry(employee, company, employee_advance_name, return_amount,
'is_advance': 'Yes'
})
bank_amount = flt(return_amount) if bank_cash_account.account_currency==currency \
else flt(return_amount) * flt(exchange_rate)
je.append("accounts", {
"account": return_account.account,
"debit_in_account_currency": return_amount,
"account_currency": return_account.account_currency,
"account_type": return_account.account_type
"account": bank_cash_account.account,
"debit_in_account_currency": bank_amount,
"account_currency": bank_cash_account.account_currency,
"account_type": bank_cash_account.account_type,
"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1
})
return je.as_dict()
def get_voucher_type(mode_of_payment=None):
voucher_type = "Cash Entry"
if mode_of_payment:
mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
if mode_of_payment_type == "Bank":
voucher_type = "Bank Entry"
return voucher_type

View File

@@ -3,15 +3,17 @@
# See license.txt
from __future__ import unicode_literals
import frappe
import frappe, erpnext
import unittest
from frappe.utils import nowdate
from erpnext.hr.doctype.employee_advance.employee_advance import make_bank_entry
from erpnext.hr.doctype.employee_advance.employee_advance import EmployeeAdvanceOverPayment
from erpnext.hr.doctype.employee.test_employee import make_employee
class TestEmployeeAdvance(unittest.TestCase):
def test_paid_amount_and_status(self):
advance = make_employee_advance()
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name)
journal_entry = make_payment_entry(advance)
journal_entry.submit()
@@ -33,11 +35,13 @@ def make_payment_entry(advance):
return journal_entry
def make_employee_advance():
def make_employee_advance(employee_name):
doc = frappe.new_doc("Employee Advance")
doc.employee = "_T-Employee-00001"
doc.employee = employee_name
doc.company = "_Test company"
doc.purpose = "For site visit"
doc.currency = erpnext.get_company_currency("_Test company")
doc.exchange_rate = 1
doc.advance_amount = 1000
doc.posting_date = nowdate()
doc.advance_account = "_Test Employee Advance - _TC"

View File

@@ -1,167 +1,69 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "Prompt",
"beta": 0,
"creation": "2018-04-13 16:14:24.174138",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "Prompt",
"creation": "2018-04-13 16:14:24.174138",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"default_salary_structure"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_leave_policy",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Leave Policy",
"length": 0,
"no_copy": 0,
"options": "Leave Policy",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_salary_structure",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Salary Structure",
"length": 0,
"no_copy": 0,
"options": "Salary Structure",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Salary Structure"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-09-18 17:17:45.617624",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-08-26 13:12:07.815330",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Grade",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}

View File

@@ -7,6 +7,7 @@ import unittest
from frappe.utils import random_string, nowdate
from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.hr.doctype.employee.test_employee import make_employee
test_records = frappe.get_test_records('Expense Claim')
test_dependencies = ['Employee']
@@ -126,6 +127,9 @@ def generate_taxes():
def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None):
employee = frappe.db.get_value("Employee", {"status": "Active"})
if not employee:
employee = make_employee("test_employee@expense_claim.com", company=company)
currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
expense_claim = {
"doctype": "Expense Claim",

View File

@@ -71,9 +71,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"oldfieldname": "tax_amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency"
"options": "currency"
},
{
"columns": 2,
@@ -81,9 +79,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total",
"oldfieldname": "total",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"options": "currency",
"read_only": 1
},
{
@@ -106,7 +102,7 @@
],
"istable": 1,
"links": [],
"modified": "2020-05-11 19:01:26.611758",
"modified": "2020-09-23 20:27:36.027728",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Taxes and Charges",

View File

@@ -21,6 +21,7 @@
"show_leaves_of_all_department_members_in_calendar",
"auto_leave_encashment",
"restrict_backdated_leave_application",
"automatically_allocate_leaves_based_on_leave_policy",
"hiring_settings",
"check_vacancies"
],
@@ -41,7 +42,7 @@
"description": "Employee records are created using the selected field",
"fieldname": "emp_created_by",
"fieldtype": "Select",
"label": "Employee Records to Be Created By",
"label": "Employee Records to be created by",
"options": "Naming Series\nEmployee Number\nFull Name"
},
{
@@ -117,7 +118,7 @@
"default": "0",
"fieldname": "restrict_backdated_leave_application",
"fieldtype": "Check",
"label": "Restrict Backdated Leave Applications"
"label": "Restrict Backdated Leave Application"
},
{
"depends_on": "eval:doc.restrict_backdated_leave_application == 1",
@@ -125,13 +126,19 @@
"fieldtype": "Link",
"label": "Role Allowed to Create Backdated Leave Application",
"options": "Role"
},
{
"default": "0",
"fieldname": "automatically_allocate_leaves_based_on_leave_policy",
"fieldtype": "Check",
"label": "Automatically Allocate Leaves Based On Leave Policy"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
"modified": "2020-10-13 11:49:46.168027",
"modified": "2020-08-27 14:30:28.995324",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",

View File

@@ -1,4 +1,5 @@
{
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-02-20 19:10:38",
@@ -24,6 +25,7 @@
"compensatory_request",
"leave_period",
"leave_policy",
"leave_policy_assignment",
"carry_forwarded_leaves_count",
"expired",
"amended_from",
@@ -160,9 +162,10 @@
"read_only": 1
},
{
"fetch_from": "employee.leave_policy",
"fetch_from": "leave_policy_assignment.leave_policy",
"fieldname": "leave_policy",
"fieldtype": "Link",
"hidden": 1,
"in_standard_filter": 1,
"label": "Leave Policy",
"options": "Leave Policy",
@@ -209,12 +212,21 @@
"fieldtype": "Float",
"label": "Carry Forwarded Leaves",
"read_only": 1
},
{
"fieldname": "leave_policy_assignment",
"fieldtype": "Link",
"label": "Leave Policy Assignment",
"options": "Leave Policy Assignment",
"read_only": 1
}
],
"icon": "fa fa-ok",
"idx": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"modified": "2019-08-08 15:08:42.440909",
"links": [],
"modified": "2020-08-20 14:25:10.314323",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",

View File

@@ -51,9 +51,19 @@ class LeaveAllocation(Document):
def on_cancel(self):
self.create_leave_ledger_entry(submit=False)
if self.leave_policy_assignment:
self.update_leave_policy_assignments_when_no_allocations_left()
if self.carry_forward:
self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
def update_leave_policy_assignments_when_no_allocations_left(self):
allocations = frappe.db.get_list("Leave Allocation", filters = {
"docstatus": 1,
"leave_policy_assignment": self.leave_policy_assignment
})
if len(allocations) == 0:
frappe.db.set_value("Leave Policy Assignment", self.leave_policy_assignment ,"leaves_allocated", 0)
def validate_period(self):
if date_diff(self.to_date, self.from_date) <= 0:
frappe.throw(_("To date cannot be before from date"))

View File

@@ -130,8 +130,7 @@ class LeaveApplication(Document):
if self.status == "Approved":
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
date = dt.strftime("%Y-%m-%d")
status = "Half Day" if getdate(date) == getdate(self.half_day_date) else "On Leave"
status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
attendance_date = date, docstatus = ('!=', 2)))
@@ -293,7 +292,8 @@ class LeaveApplication(Document):
def set_half_day_date(self):
if self.from_date == self.to_date and self.half_day == 1:
self.half_day_date = self.from_date
elif self.half_day == 0:
if self.half_day == 0:
self.half_day_date = None
def notify_employee(self):
@@ -376,24 +376,32 @@ class LeaveApplication(Document):
if expiry_date:
self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit, lwp)
else:
raise_exception = True
if frappe.flags.in_patch:
raise_exception=False
args = dict(
leaves=self.total_leave_days * -1,
from_date=self.from_date,
to_date=self.to_date,
is_lwp=lwp,
holiday_list=get_holiday_list_for_employee(self.employee)
holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
)
create_leave_ledger_entry(self, args, submit)
def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit, lwp):
''' splits leave application into two ledger entries to consider expiry of allocation '''
raise_exception = True
if frappe.flags.in_patch:
raise_exception=False
args = dict(
from_date=self.from_date,
to_date=expiry_date,
leaves=(date_diff(expiry_date, self.from_date) + 1) * -1,
is_lwp=lwp,
holiday_list=get_holiday_list_for_employee(self.employee),
holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
)
create_leave_ledger_entry(self, args, submit)

View File

@@ -10,6 +10,7 @@ from frappe.permissions import clear_user_permissions_for_doctype
from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
test_dependencies = ["Leave Allocation", "Leave Block List"]
@@ -410,25 +411,39 @@ class TestLeaveApplication(unittest.TestCase):
self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8)), 21)
def test_earned_leaves_creation(self):
frappe.db.sql('''delete from `tabLeave Period`''')
frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
frappe.db.sql('''delete from `tabLeave Allocation`''')
frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
leave_period = get_leave_period()
employee = get_employee()
leave_type = 'Test Earned Leave Type'
if not frappe.db.exists('Leave Type', leave_type):
frappe.get_doc(dict(
leave_type_name = leave_type,
doctype = 'Leave Type',
is_earned_leave = 1,
earned_leave_frequency = 'Monthly',
rounding = 0.5,
max_leaves_allowed = 6
)).insert()
frappe.delete_doc_if_exists("Leave Type", 'Test Earned Leave Type', force=1)
frappe.get_doc(dict(
leave_type_name = leave_type,
doctype = 'Leave Type',
is_earned_leave = 1,
earned_leave_frequency = 'Monthly',
rounding = 0.5,
max_leaves_allowed = 6
)).insert()
leave_policy = frappe.get_doc({
"doctype": "Leave Policy",
"leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}]
}).insert()
frappe.db.set_value("Employee", employee.name, "leave_policy", leave_policy.name)
allocate_leaves(employee, leave_period, leave_type, 0, eligible_leaves = 12)
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
"leave_period": leave_period.name
}
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
from erpnext.hr.utils import allocate_earned_leaves
i = 0

View File

@@ -22,7 +22,12 @@ frappe.ui.form.on('Leave Encashment', {
}
},
employee: function(frm) {
frm.trigger("get_leave_details_for_encashment");
if (frm.doc.employee) {
frappe.run_serially([
() => frm.trigger('get_employee_currency'),
() => frm.trigger('get_leave_details_for_encashment')
]);
}
},
leave_type: function(frm) {
frm.trigger("get_leave_details_for_encashment");
@@ -40,5 +45,20 @@ frappe.ui.form.on('Leave Encashment', {
}
});
}
}
},
get_employee_currency: function(frm) {
frappe.call({
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
args: {
employee: frm.doc.employee,
},
callback: function(r) {
if (r.message) {
frm.set_value('currency', r.message);
frm.refresh_fields();
}
}
});
},
});

View File

@@ -12,6 +12,7 @@
"employee",
"employee_name",
"department",
"company",
"column_break_4",
"leave_type",
"leave_allocation",
@@ -19,9 +20,11 @@
"encashable_days",
"amended_from",
"payroll",
"encashment_amount",
"encashment_date",
"additional_salary"
"additional_salary",
"column_break_14",
"currency",
"encashment_amount"
],
"fields": [
{
@@ -109,6 +112,7 @@
"in_list_view": 1,
"label": "Encashment Amount",
"no_copy": 1,
"options": "currency",
"read_only": 1
},
{
@@ -124,11 +128,34 @@
"no_copy": 1,
"options": "Additional Salary",
"read_only": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency",
"print_hide": 1,
"read_only": 1,
"reqd": 1
},
{
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2019-12-16 11:51:57.732223",
"modified": "2020-11-25 11:56:06.777241",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Encashment",

View File

@@ -16,10 +16,16 @@ class LeaveEncashment(Document):
def validate(self):
set_employee_name(self)
self.get_leave_details_for_encashment()
self.validate_salary_structure()
if not self.encashment_date:
self.encashment_date = getdate(nowdate())
def validate_salary_structure(self):
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
def before_submit(self):
if self.encashment_amount <= 0:
frappe.throw(_("You can only submit Leave Encashment for a valid encashment amount"))
@@ -30,9 +36,10 @@ class LeaveEncashment(Document):
additional_salary = frappe.new_doc("Additional Salary")
additional_salary.company = frappe.get_value("Employee", self.employee, "company")
additional_salary.employee = self.employee
additional_salary.currency = self.currency
earning_component = frappe.get_value("Leave Type", self.leave_type, "earning_component")
if not earning_component:
frappe.throw(_("Please set Earning Component for Leave type: {0}.".format(self.leave_type)))
frappe.throw(_("Please set Earning Component for Leave type: {0}.").format(self.leave_type))
additional_salary.salary_component = earning_component
additional_salary.payroll_date = self.encashment_date
additional_salary.amount = self.encashment_amount
@@ -98,7 +105,11 @@ class LeaveEncashment(Document):
create_leave_ledger_entry(self, args, submit)
# create reverse entry for expired leaves
to_date = self.get_leave_allocation().get('to_date')
leave_allocation = self.get_leave_allocation()
if not leave_allocation:
return
to_date = leave_allocation.get('to_date')
if to_date < getdate(nowdate()):
args = frappe._dict(
leaves=self.encashable_days,

View File

@@ -9,6 +9,7 @@ from frappe.utils import today, add_months
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy\
test_dependencies = ["Leave Type"]
@@ -16,6 +17,7 @@ test_dependencies = ["Leave Type"]
class TestLeaveEncashment(unittest.TestCase):
def setUp(self):
frappe.db.sql('''delete from `tabLeave Period`''')
frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
frappe.db.sql('''delete from `tabLeave Allocation`''')
frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
frappe.db.sql('''delete from `tabAdditional Salary`''')
@@ -29,14 +31,26 @@ class TestLeaveEncashment(unittest.TestCase):
# create employee, salary structure and assignment
self.employee = make_employee("test_employee_encashment@example.com")
frappe.db.set_value("Employee", self.employee, "leave_policy", leave_policy.name)
self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
"leave_period": self.leave_period.name
}
leave_policy_assignments = create_assignment_for_multiple_employees([self.employee], frappe._dict(data))
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
other_details={"leave_encashment_amount_per_day": 50})
# create the leave period and assign the leaves
self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
self.leave_period.grant_leave_allocation(employee=self.employee)
#grant Leaves
frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
def tearDown(self):
for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
frappe.db.sql("delete from `tab%s`" % dt)
def test_leave_balance_value_and_amount(self):
frappe.db.sql('''delete from `tabLeave Encashment`''')
@@ -45,7 +59,8 @@ class TestLeaveEncashment(unittest.TestCase):
employee=self.employee,
leave_type="_Test Leave Type Encashment",
leave_period=self.leave_period.name,
payroll_date=today()
payroll_date=today(),
currency="INR"
)).insert()
self.assertEqual(leave_encashment.leave_balance, 10)
@@ -65,7 +80,8 @@ class TestLeaveEncashment(unittest.TestCase):
employee=self.employee,
leave_type="_Test Leave Type Encashment",
leave_period=self.leave_period.name,
payroll_date=today()
payroll_date=today(),
currency="INR"
)).insert()
leave_encashment.submit()

View File

@@ -2,14 +2,6 @@
// For license information, please see license.txt
frappe.ui.form.on('Leave Period', {
refresh: (frm)=>{
frm.set_df_property("grant_leaves", "hidden", frm.doc.__islocal ? 1:0);
if(!frm.is_new()) {
frm.add_custom_button(__('Grant Leaves'), function () {
frm.trigger("grant_leaves");
});
}
},
from_date: (frm)=>{
if (frm.doc.from_date && !frm.doc.to_date) {
var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12);
@@ -22,73 +14,7 @@ frappe.ui.form.on('Leave Period', {
"filters": {
"company": frm.doc.company,
}
}
})
},
grant_leaves: function(frm) {
var d = new frappe.ui.Dialog({
title: __('Grant Leaves'),
fields: [
{
"label": "Filter Employees By (Optional)",
"fieldname": "sec_break",
"fieldtype": "Section Break",
},
{
"label": "Employee Grade",
"fieldname": "grade",
"fieldtype": "Link",
"options": "Employee Grade"
},
{
"label": "Department",
"fieldname": "department",
"fieldtype": "Link",
"options": "Department"
},
{
"fieldname": "col_break",
"fieldtype": "Column Break",
},
{
"label": "Designation",
"fieldname": "designation",
"fieldtype": "Link",
"options": "Designation"
},
{
"label": "Employee",
"fieldname": "employee",
"fieldtype": "Link",
"options": "Employee"
},
{
"fieldname": "sec_break",
"fieldtype": "Section Break",
},
{
"label": "Add unused leaves from previous allocations",
"fieldname": "carry_forward",
"fieldtype": "Check"
}
],
primary_action: function() {
var data = d.get_values();
frappe.call({
doc: frm.doc,
method: "grant_leave_allocation",
args: data,
callback: function(r) {
if(!r.exc) {
d.hide();
frm.reload_doc();
}
}
});
},
primary_action_label: __('Grant')
};
});
d.show();
}
},
});

View File

@@ -7,24 +7,10 @@ import frappe
from frappe import _
from frappe.utils import getdate, cstr, add_days, date_diff, getdate, ceil
from frappe.model.document import Document
from erpnext.hr.utils import validate_overlap, get_employee_leave_policy
from erpnext.hr.utils import validate_overlap
from frappe.utils.background_jobs import enqueue
from six import iteritems
class LeavePeriod(Document):
def get_employees(self, args):
conditions, values = [], []
for field, value in iteritems(args):
if value:
conditions.append("{0}=%s".format(field))
values.append(value)
condition_str = " and " + " and ".join(conditions) if len(conditions) else ""
employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" #nosec
.format(condition=condition_str), tuple(values)))
return employees
def validate(self):
self.validate_dates()
@@ -33,96 +19,3 @@ class LeavePeriod(Document):
def validate_dates(self):
if getdate(self.from_date) >= getdate(self.to_date):
frappe.throw(_("To date can not be equal or less than from date"))
def grant_leave_allocation(self, grade=None, department=None, designation=None,
employee=None, carry_forward=0):
employee_records = self.get_employees({
"grade": grade,
"department": department,
"designation": designation,
"name": employee
})
if employee_records:
if len(employee_records) > 20:
frappe.enqueue(grant_leave_alloc_for_employees, timeout=600,
employee_records=employee_records, leave_period=self, carry_forward=carry_forward)
else:
grant_leave_alloc_for_employees(employee_records, self, carry_forward)
else:
frappe.msgprint(_("No Employee Found"))
def grant_leave_alloc_for_employees(employee_records, leave_period, carry_forward=0):
leave_allocations = []
existing_allocations_for = get_existing_allocations(list(employee_records.keys()), leave_period.name)
leave_type_details = get_leave_type_details()
count = 0
for employee in employee_records.keys():
if employee in existing_allocations_for:
continue
count +=1
leave_policy = get_employee_leave_policy(employee)
if leave_policy:
for leave_policy_detail in leave_policy.leave_policy_details:
if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
leave_allocation = create_leave_allocation(employee, leave_policy_detail.leave_type,
leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward, employee_records.get(employee))
leave_allocations.append(leave_allocation)
frappe.db.commit()
frappe.publish_progress(count*100/len(set(employee_records.keys()) - set(existing_allocations_for)), title = _("Allocating leaves..."))
if leave_allocations:
frappe.msgprint(_("Leaves has been granted sucessfully"))
def get_existing_allocations(employees, leave_period):
leave_allocations = frappe.db.sql_list("""
SELECT DISTINCT
employee
FROM `tabLeave Allocation`
WHERE
leave_period=%s
AND employee in (%s)
AND carry_forward=0
AND docstatus=1
""" % ('%s', ', '.join(['%s']*len(employees))), [leave_period] + employees)
if leave_allocations:
frappe.msgprint(_("Skipping Leave Allocation for the following employees, as Leave Allocation records already exists against them. {0}")
.format("\n".join(leave_allocations)))
return leave_allocations
def get_leave_type_details():
leave_type_details = frappe._dict()
leave_types = frappe.get_all("Leave Type",
fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"])
for d in leave_types:
leave_type_details.setdefault(d.name, d)
return leave_type_details
def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward, date_of_joining):
''' Creates leave allocation for the given employee in the provided leave period '''
if carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
carry_forward = 0
# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
if getdate(date_of_joining) > getdate(leave_period.from_date):
remaining_period = ((date_diff(leave_period.to_date, date_of_joining) + 1) / (date_diff(leave_period.to_date, leave_period.from_date) + 1))
new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
# Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1:
new_leaves_allocated = 0
allocation = frappe.get_doc(dict(
doctype="Leave Allocation",
employee=employee,
leave_type=leave_type,
from_date=leave_period.from_date,
to_date=leave_period.to_date,
new_leaves_allocated=new_leaves_allocated,
leave_period=leave_period.name,
carry_forward=carry_forward
))
allocation.save(ignore_permissions = True)
allocation.submit()
return allocation.name

View File

@@ -5,43 +5,11 @@ from __future__ import unicode_literals
import frappe, erpnext
import unittest
from frappe.utils import today, add_months
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
test_dependencies = ["Employee", "Leave Type", "Leave Policy"]
class TestLeavePeriod(unittest.TestCase):
def setUp(self):
frappe.db.sql("delete from `tabLeave Period`")
def test_leave_grant(self):
leave_type = "_Test Leave Type"
# create the leave policy
leave_policy = frappe.get_doc({
"doctype": "Leave Policy",
"leave_policy_details": [{
"leave_type": leave_type,
"annual_allocation": 20
}]
}).insert()
leave_policy.submit()
# create employee and assign the leave period
employee = "test_leave_period@employee.com"
employee_doc_name = make_employee(employee)
frappe.db.set_value("Employee", employee_doc_name, "leave_policy", leave_policy.name)
# clear the already allocated leave
frappe.db.sql('''delete from `tabLeave Allocation` where employee=%s''', "test_leave_period@employee.com")
# create the leave period
leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
# test leave_allocation
leave_period.grant_leave_allocation(employee=employee_doc_name)
self.assertEqual(get_leave_balance_on(employee_doc_name, leave_type, today()), 20)
pass
def create_leave_period(from_date, to_date, company=None):
leave_period = frappe.db.get_value('Leave Period',

View File

@@ -4,22 +4,10 @@ from frappe import _
def get_data():
return {
'fieldname': 'leave_policy',
'non_standard_fieldnames': {
'Employee Grade': 'default_leave_policy'
},
'transactions': [
{
'label': _('Employees'),
'items': ['Employee', 'Employee Grade']
},
{
'label': _('Leaves'),
'items': ['Leave Allocation']
},
]
}
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Leave Policy Assignment', {
onload: function(frm) {
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
},
refresh: function(frm) {
if (frm.doc.docstatus === 1 && frm.doc.leaves_allocated === 0) {
frm.add_custom_button(__("Grant Leave"), function() {
frappe.call({
doc: frm.doc,
method: "grant_leave_alloc_for_employee",
callback: function(r) {
let leave_allocations = r.message;
let msg = frm.events.get_success_message(leave_allocations);
frappe.msgprint(msg);
cur_frm.refresh();
}
});
});
}
},
get_success_message: function(leave_allocations) {
let msg = __("Leaves has been granted successfully");
msg += "<br><table class='table table-bordered'>";
msg += "<tr><th>"+__('Leave Type')+"</th><th>"+__("Leave Allocation")+"</th><th>"+__("Leaves Granted")+"</th><tr>";
for (let key in leave_allocations) {
msg += "<tr><th>"+key+"</th><td>"+leave_allocations[key]["name"]+"</td><td>"+leave_allocations[key]["leaves"]+"</td></tr>";
}
msg += "</table>";
return msg;
},
assignment_based_on: function(frm) {
if (frm.doc.assignment_based_on) {
frm.events.set_effective_date(frm);
} else {
frm.set_value("effective_from", '');
frm.set_value("effective_to", '');
}
},
leave_period: function(frm) {
if (frm.doc.leave_period) {
frm.events.set_effective_date(frm);
}
},
set_effective_date: function(frm) {
if (frm.doc.assignment_based_on == "Leave Period" && frm.doc.leave_period) {
frappe.model.with_doc("Leave Period", frm.doc.leave_period, function () {
let from_date = frappe.model.get_value("Leave Period", frm.doc.leave_period, "from_date");
let to_date = frappe.model.get_value("Leave Period", frm.doc.leave_period, "to_date");
frm.set_value("effective_from", from_date);
frm.set_value("effective_to", to_date);
});
} else if (frm.doc.assignment_based_on == "Joining Date" && frm.doc.employee) {
frappe.model.with_doc("Employee", frm.doc.employee, function () {
let from_date = frappe.model.get_value("Employee", frm.doc.employee, "date_of_joining");
frm.set_value("effective_from", from_date);
frm.set_value("effective_to", frappe.datetime.add_months(frm.doc.effective_from, 12));
});
}
frm.refresh();
}
});

View File

@@ -0,0 +1,163 @@
{
"actions": [],
"autoname": "HR-LPOL-ASSGN-.#####",
"creation": "2020-08-19 13:02:43.343666",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"employee",
"employee_name",
"company",
"leave_policy",
"carry_forward",
"column_break_5",
"assignment_based_on",
"leave_period",
"effective_from",
"effective_to",
"leaves_allocated",
"amended_from"
],
"fields": [
{
"fieldname": "employee",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Employee",
"options": "Employee",
"reqd": 1
},
{
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"label": "Employee name",
"read_only": 1
},
{
"fieldname": "leave_policy",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Leave Policy",
"options": "Leave Policy",
"reqd": 1
},
{
"fieldname": "assignment_based_on",
"fieldtype": "Select",
"label": "Assignment based on",
"options": "\nLeave Period\nJoining Date"
},
{
"depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
"fieldname": "leave_period",
"fieldtype": "Link",
"label": "Leave Period",
"mandatory_depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
"options": "Leave Period"
},
{
"fieldname": "effective_from",
"fieldtype": "Date",
"label": "Effective From",
"read_only_depends_on": "eval:doc.assignment_based_on",
"reqd": 1
},
{
"fieldname": "effective_to",
"fieldtype": "Date",
"label": "Effective To",
"read_only_depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
"reqd": 1
},
{
"fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Company",
"options": "Company",
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Leave Policy Assignment",
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"fieldname": "carry_forward",
"fieldtype": "Check",
"label": "Add unused leaves from previous allocations"
},
{
"default": "0",
"fieldname": "leaves_allocated",
"fieldtype": "Check",
"hidden": 1,
"label": "Leaves Allocated"
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-12-17 16:27:20.311060",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Policy Assignment",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"submit": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, 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
from frappe import _, bold
from frappe.utils import getdate, date_diff, comma_and, formatdate
from math import ceil
import json
from six import string_types
class LeavePolicyAssignment(Document):
def validate(self):
self.validate_policy_assignment_overlap()
self.set_dates()
def set_dates(self):
if self.assignment_based_on == "Leave Period":
self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
elif self.assignment_based_on == "Joining Date":
self.effective_from = frappe.db.get_value("Employee", self.employee, "date_of_joining")
def validate_policy_assignment_overlap(self):
leave_policy_assignments = frappe.get_all("Leave Policy Assignment", filters = {
"employee": self.employee,
"name": ("!=", self.name),
"docstatus": 1,
"effective_to": (">=", self.effective_from),
"effective_from": ("<=", self.effective_to)
})
if len(leave_policy_assignments):
frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}")
.format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to))))
def grant_leave_alloc_for_employee(self):
if self.leaves_allocated:
frappe.throw(_("Leave already have been assigned for this Leave Policy Assignment"))
else:
leave_allocations = {}
leave_type_details = get_leave_type_details()
leave_policy = frappe.get_doc("Leave Policy", self.leave_policy)
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
for leave_policy_detail in leave_policy.leave_policy_details:
if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
leave_allocation, new_leaves_allocated = self.create_leave_allocation(
leave_policy_detail.leave_type, leave_policy_detail.annual_allocation,
leave_type_details, date_of_joining
)
leave_allocations[leave_policy_detail.leave_type] = {"name": leave_allocation, "leaves": new_leaves_allocated}
self.db_set("leaves_allocated", 1)
return leave_allocations
def create_leave_allocation(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
# Creates leave allocation for the given employee in the provided leave period
carry_forward = self.carry_forward
if self.carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
carry_forward = 0
new_leaves_allocated = self.get_new_leaves(leave_type, new_leaves_allocated,
leave_type_details, date_of_joining)
allocation = frappe.get_doc(dict(
doctype="Leave Allocation",
employee=self.employee,
leave_type=leave_type,
from_date=self.effective_from,
to_date=self.effective_to,
new_leaves_allocated=new_leaves_allocated,
leave_period=self.leave_period or None,
leave_policy_assignment = self.name,
leave_policy = self.leave_policy,
carry_forward=carry_forward
))
allocation.save(ignore_permissions = True)
allocation.submit()
return allocation.name, new_leaves_allocated
def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
if getdate(date_of_joining) > getdate(self.effective_from):
remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1))
new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
# Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1:
new_leaves_allocated = 0
return new_leaves_allocated
@frappe.whitelist()
def grant_leave_for_multiple_employees(leave_policy_assignments):
leave_policy_assignments = json.loads(leave_policy_assignments)
not_granted = []
for assignment in leave_policy_assignments:
try:
frappe.get_doc("Leave Policy Assignment", assignment).grant_leave_alloc_for_employee()
except Exception:
not_granted.append(assignment)
if len(not_granted):
msg = _("Leave not Granted for Assignments:")+ bold(comma_and(not_granted)) + _(". Please Check documents")
else:
msg = _("Leave granted Successfully")
frappe.msgprint(msg)
@frappe.whitelist()
def create_assignment_for_multiple_employees(employees, data):
if isinstance(employees, string_types):
employees= json.loads(employees)
if isinstance(data, string_types):
data = frappe._dict(json.loads(data))
docs_name = []
for employee in employees:
assignment = frappe.new_doc("Leave Policy Assignment")
assignment.employee = employee
assignment.assignment_based_on = data.assignment_based_on or None
assignment.leave_policy = data.leave_policy
assignment.effective_from = getdate(data.effective_from) or None
assignment.effective_to = getdate(data.effective_to) or None
assignment.leave_period = data.leave_period or None
assignment.carry_forward = data.carry_forward
assignment.save()
assignment.submit()
docs_name.append(assignment.name)
return docs_name
def automatically_allocate_leaves_based_on_leave_policy():
today = getdate()
automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_single_value(
'HR Settings', 'automatically_allocate_leaves_based_on_leave_policy'
)
pending_assignments = frappe.get_list(
"Leave Policy Assignment",
filters = {"docstatus": 1, "leaves_allocated": 0, "effective_from": today}
)
if len(pending_assignments) and automatically_allocate_leaves_based_on_leave_policy:
for assignment in pending_assignments:
frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
def get_leave_type_details():
leave_type_details = frappe._dict()
leave_types = frappe.get_all("Leave Type",
fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"])
for d in leave_types:
leave_type_details.setdefault(d.name, d)
return leave_type_details

View File

@@ -0,0 +1,138 @@
frappe.listview_settings['Leave Policy Assignment'] = {
onload: function (list_view) {
let me = this;
list_view.page.add_inner_button(__("Bulk Leave Policy Assignment"), function () {
me.dialog = new frappe.ui.form.MultiSelectDialog({
doctype: "Employee",
target: cur_list,
setters: {
company: '',
department: '',
},
data_fields: [{
fieldname: 'leave_policy',
fieldtype: 'Link',
options: 'Leave Policy',
label: __('Leave Policy'),
reqd: 1
},
{
fieldname: 'assignment_based_on',
fieldtype: 'Select',
options: ["", "Leave Period"],
label: __('Assignment Based On'),
onchange: () => {
if (cur_dialog.fields_dict.assignment_based_on.value === "Leave Period") {
cur_dialog.set_df_property("effective_from", "read_only", 1);
cur_dialog.set_df_property("leave_period", "reqd", 1);
cur_dialog.set_df_property("effective_to", "read_only", 1);
} else {
cur_dialog.set_df_property("effective_from", "read_only", 0);
cur_dialog.set_df_property("leave_period", "reqd", 0);
cur_dialog.set_df_property("effective_to", "read_only", 0);
cur_dialog.set_value("effective_from", "");
cur_dialog.set_value("effective_to", "");
}
}
},
{
fieldname: "leave_period",
fieldtype: 'Link',
options: "Leave Period",
label: __('Leave Period'),
depends_on: doc => {
return doc.assignment_based_on == 'Leave Period';
},
onchange: () => {
if (cur_dialog.fields_dict.leave_period.value) {
me.set_effective_date();
}
}
},
{
fieldtype: "Column Break"
},
{
fieldname: 'effective_from',
fieldtype: 'Date',
label: __('Effective From'),
reqd: 1
},
{
fieldname: 'effective_to',
fieldtype: 'Date',
label: __('Effective To'),
reqd: 1
},
{
fieldname: 'carry_forward',
fieldtype: 'Check',
label: __('Add unused leaves from previous allocations')
}
],
get_query() {
return {
filters: {
status: ['=', 'Active']
}
};
},
add_filters_group: 1,
primary_action_label: "Assign",
action(employees, data) {
frappe.call({
method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.create_assignment_for_multiple_employees',
async: false,
args: {
employees: employees,
data: data
}
});
cur_dialog.hide();
}
});
});
list_view.page.add_inner_button(__("Grant Leaves"), function () {
me.dialog = new frappe.ui.form.MultiSelectDialog({
doctype: "Leave Policy Assignment",
target: cur_list,
setters: {
company: '',
employee: '',
},
get_query() {
return {
filters: {
docstatus: ['=', 1],
leaves_allocated: ['=', 0]
}
};
},
add_filters_group: 1,
primary_action_label: "Grant Leaves",
action(leave_policy_assignments) {
frappe.call({
method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.grant_leave_for_multiple_employees',
async: false,
args: {
leave_policy_assignments: leave_policy_assignments
}
});
me.dialog.hide();
}
});
});
},
set_effective_date: function () {
if (cur_dialog.fields_dict.assignment_based_on.value === "Leave Period" && cur_dialog.fields_dict.leave_period.value) {
frappe.model.with_doc("Leave Period", cur_dialog.fields_dict.leave_period.value, function () {
let from_date = frappe.model.get_value("Leave Period", cur_dialog.fields_dict.leave_period.value, "from_date");
let to_date = frappe.model.get_value("Leave Period", cur_dialog.fields_dict.leave_period.value, "to_date");
cur_dialog.set_value("effective_from", from_date);
cur_dialog.set_value("effective_to", to_date);
});
}
}
};

View File

@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from erpnext.hr.doctype.leave_application.test_leave_application import get_leave_period, get_employee
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy
class TestLeavePolicyAssignment(unittest.TestCase):
def setUp(self):
for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
def test_grant_leaves(self):
leave_period = get_leave_period()
employee = get_employee()
# create the leave policy with leave type "_Test Leave Type", allocation = 10
leave_policy = create_leave_policy()
leave_policy.submit()
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
"leave_period": leave_period.name
}
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
leave_policy_assignment_doc.grant_leave_alloc_for_employee()
leave_policy_assignment_doc.reload()
self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
leave_allocation = frappe.get_list("Leave Allocation", filters={
"employee": employee.name,
"leave_policy":leave_policy.name,
"leave_policy_assignment": leave_policy_assignments[0],
"docstatus": 1})[0]
leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
self.assertEqual(leave_alloc_doc.new_leaves_allocated, 10)
self.assertEqual(leave_alloc_doc.leave_type, "_Test Leave Type")
self.assertEqual(leave_alloc_doc.from_date, leave_period.from_date)
self.assertEqual(leave_alloc_doc.to_date, leave_period.to_date)
self.assertEqual(leave_alloc_doc.leave_policy, leave_policy.name)
self.assertEqual(leave_alloc_doc.leave_policy_assignment, leave_policy_assignments[0])
def test_allow_to_grant_all_leave_after_cancellation_of_every_leave_allocation(self):
leave_period = get_leave_period()
employee = get_employee()
# create the leave policy with leave type "_Test Leave Type", allocation = 10
leave_policy = create_leave_policy()
leave_policy.submit()
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
"leave_period": leave_period.name
}
leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
leave_policy_assignment_doc.grant_leave_alloc_for_employee()
leave_policy_assignment_doc.reload()
# every leave is allocated no more leave can be granted now
self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
leave_allocation = frappe.get_list("Leave Allocation", filters={
"employee": employee.name,
"leave_policy":leave_policy.name,
"leave_policy_assignment": leave_policy_assignments[0],
"docstatus": 1})[0]
leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
# User all allowed to grant leave when there is no allocation against assignment
leave_alloc_doc.cancel()
leave_alloc_doc.delete()
leave_policy_assignment_doc.reload()
# User are now allowed to grant leave
self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 0)
def tearDown(self):
for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec

View File

@@ -15,6 +15,8 @@
"column_break_3",
"is_carry_forward",
"is_lwp",
"is_ppl",
"fraction_of_daily_salary_per_leave",
"is_optional_leave",
"allow_negative",
"include_holiday",
@@ -31,6 +33,7 @@
"is_earned_leave",
"earned_leave_frequency",
"column_break_22",
"based_on_date_of_joining",
"rounding"
],
"fields": [
@@ -77,6 +80,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.is_ppl == 0",
"fieldname": "is_lwp",
"fieldtype": "Check",
"label": "Is Leave Without Pay"
@@ -183,12 +187,33 @@
{
"fieldname": "column_break_22",
"fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "eval:doc.is_earned_leave",
"description": "If checked, leave will be granted on the day of joining every month.",
"fieldname": "based_on_date_of_joining",
"fieldtype": "Check",
"label": "Based On Date Of Joining"
},
{
"depends_on": "eval:doc.is_lwp == 0",
"fieldname": "is_ppl",
"fieldtype": "Check",
"label": "Is Partially Paid Leave"
},
{
"depends_on": "eval:doc.is_ppl == 1",
"fieldname": "fraction_of_daily_salary_per_leave",
"fieldtype": "Float",
"label": "Fraction of Daily Salary per Leave",
"mandatory_depends_on": "eval:doc.is_ppl == 1"
}
],
"icon": "fa fa-flag",
"idx": 1,
"links": [],
"modified": "2019-12-12 12:48:37.780254",
"modified": "2020-10-15 15:49:47.555105",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Type",

View File

@@ -21,3 +21,9 @@ class LeaveType(Document):
leave_allocation = [l['name'] for l in leave_allocation]
if leave_allocation:
frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec
if self.is_lwp and self.is_ppl:
frappe.throw(_("Leave Type can be either without pay or partial pay"))
if self.is_ppl and (self.fraction_of_daily_salary_per_leave < 0 or self.fraction_of_daily_salary_per_leave > 1):
frappe.throw(_("The fraction of Daily Salary per Leave should be between 0 and 1"))

View File

@@ -18,9 +18,14 @@ def create_leave_type(**args):
"allow_encashment": args.allow_encashment or 0,
"is_earned_leave": args.is_earned_leave or 0,
"is_lwp": args.is_lwp or 0,
"is_ppl":args.is_ppl or 0,
"is_carry_forward": args.is_carry_forward or 0,
"expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
"encashment_threshold_days": args.encashment_threshold_days or 5,
"earning_component": "Leave Encashment"
})
if leave_type.is_ppl:
leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
return leave_type

View File

@@ -24,10 +24,10 @@ erpnext.hr.AttendanceControlPanel = frappe.ui.form.Controller.extend({
}
window.location.href = repl(frappe.request.url +
'?cmd=%(cmd)s&from_date=%(from_date)s&to_date=%(to_date)s', {
cmd: "erpnext.hr.doctype.upload_attendance.upload_attendance.get_template",
from_date: this.frm.doc.att_fr_date,
to_date: this.frm.doc.att_to_date,
});
cmd: "erpnext.hr.doctype.upload_attendance.upload_attendance.get_template",
from_date: this.frm.doc.att_fr_date,
to_date: this.frm.doc.att_to_date,
});
},
show_upload() {

View File

@@ -28,7 +28,12 @@ def get_template():
w = UnicodeWriter()
w = add_header(w)
w = add_data(w, args)
try:
w = add_data(w, args)
except Exception as e:
frappe.clear_messages()
frappe.respond_as_web_page("Holiday List Missing", html=e)
return
# write out response as a type csv
frappe.response['result'] = cstr(w.getvalue())

View File

@@ -215,19 +215,6 @@ def throw_overlap_error(doc, exists_for, overlap_doc, from_date, to_date):
+ _(") for {0}").format(exists_for)
frappe.throw(msg)
def get_employee_leave_policy(employee):
leave_policy = frappe.db.get_value("Employee", employee, "leave_policy")
if not leave_policy:
employee_grade = frappe.db.get_value("Employee", employee, "grade")
if employee_grade:
leave_policy = frappe.db.get_value("Employee Grade", employee_grade, "default_leave_policy")
if not leave_policy:
frappe.throw(_("Employee {0} of grade {1} have no default leave policy").format(employee, employee_grade))
if leave_policy:
return frappe.get_doc("Leave Policy", leave_policy)
else:
frappe.throw(_("Please set leave policy for employee {0} in Employee / Grade record").format(employee))
def validate_duplicate_exemption_for_payroll_period(doctype, docname, payroll_period, employee):
existing_record = frappe.db.exists(doctype, {
"payroll_period": payroll_period,
@@ -300,43 +287,68 @@ def generate_leave_encashment():
def allocate_earned_leaves():
'''Allocate earned leaves to Employees'''
e_leave_types = frappe.get_all("Leave Type",
fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding"],
filters={'is_earned_leave' : 1})
e_leave_types = get_earned_leaves()
today = getdate()
divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
for e_leave_type in e_leave_types:
leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where %s
between from_date and to_date and docstatus=1 and leave_type=%s""", (today, e_leave_type.name), as_dict=1)
leave_allocations = get_leave_allocations(today, e_leave_type.name)
for allocation in leave_allocations:
leave_policy = get_employee_leave_policy(allocation.employee)
if not leave_policy:
if not allocation.leave_policy_assignment and not allocation.leave_policy:
continue
if not e_leave_type.earned_leave_frequency == "Monthly":
if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency):
continue
leave_policy = allocation.leave_policy if allocation.leave_policy else frappe.db.get_value(
"Leave Policy Assignment", allocation.leave_policy_assignment, ["leave_policy"])
annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={
'parent': leave_policy.name,
'parent': leave_policy,
'leave_type': e_leave_type.name
}, fieldname=['annual_allocation'])
if annual_allocation:
earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency]
if e_leave_type.rounding == "0.5":
earned_leaves = round(earned_leaves * 2) / 2
else:
earned_leaves = round(earned_leaves)
allocation = frappe.get_doc('Leave Allocation', allocation.name)
new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
from_date=allocation.from_date
if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
new_allocation = e_leave_type.max_leaves_allowed
if e_leave_type.based_on_date_of_joining_date:
from_date = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
if new_allocation == allocation.total_leaves_allocated:
continue
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
create_additional_leave_ledger_entry(allocation, earned_leaves, today)
if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining_date):
update_previous_leave_allocation(allocation, annual_allocation, e_leave_type)
def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type):
divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
if annual_allocation:
earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency]
if e_leave_type.rounding == "0.5":
earned_leaves = round(earned_leaves * 2) / 2
else:
earned_leaves = round(earned_leaves)
allocation = frappe.get_doc('Leave Allocation', allocation.name)
new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
new_allocation = e_leave_type.max_leaves_allowed
if new_allocation != allocation.total_leaves_allocated:
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
today_date = today()
create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
def get_leave_allocations(date, leave_type):
return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy
from `tabLeave Allocation`
where
%s between from_date and to_date and docstatus=1
and leave_type=%s""",
(date, leave_type), as_dict=1)
def get_earned_leaves():
return frappe.get_all("Leave Type",
fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding", "based_on_date_of_joining"],
filters={'is_earned_leave' : 1})
def create_additional_leave_ledger_entry(allocation, leaves, date):
''' Create leave ledger entry for leave types '''
@@ -345,24 +357,32 @@ def create_additional_leave_ledger_entry(allocation, leaves, date):
allocation.unused_leaves = 0
allocation.create_leave_ledger_entry()
def check_frequency_hit(from_date, to_date, frequency):
'''Return True if current date matches frequency'''
from_dt = get_datetime(from_date)
to_dt = get_datetime(to_date)
def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining_date):
import calendar
from dateutil import relativedelta
rd = relativedelta.relativedelta(to_dt, from_dt)
months = rd.months
if frequency == "Quarterly":
if not months % 3:
from_date = get_datetime(from_date)
to_date = get_datetime(to_date)
rd = relativedelta.relativedelta(to_date, from_date)
#last day of month
last_day = calendar.monthrange(to_date.year, to_date.month)[1]
if (from_date.day == to_date.day and based_on_date_of_joining_date) or (not based_on_date_of_joining_date and to_date.day == last_day):
if frequency == "Monthly":
return True
elif frequency == "Half-Yearly":
if not months % 6:
elif frequency == "Quarterly" and rd.months % 3:
return True
elif frequency == "Yearly":
if not months % 12:
elif frequency == "Half-Yearly" and rd.months % 6:
return True
elif frequency == "Yearly" and rd.months % 12:
return True
if frappe.flags.in_test:
return True
return False
def get_salary_assignment(employee, date):
assignment = frappe.db.sql("""
select * from `tabSalary Structure Assignment`
@@ -454,3 +474,10 @@ def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, co
if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0:
total_claimed_amount = sum_of_claimed_amount[0].total_amount
return total_claimed_amount
def grant_leaves_automatically():
automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_singles_value("HR Settings", "automatically_allocate_leaves_based_on_leave_policy")
if automatically_allocate_leaves_based_on_leave_policy:
lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0})
for assignment in lpa:
frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()