Merge branch 'develop' into patient-history-enhancements
This commit is contained in:
commit
7a69a3367c
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Community Forum
|
||||
url: https://discuss.erpnext.com/
|
||||
about: For general QnA, discussions and community help.
|
@ -6,9 +6,8 @@ import frappe, json
|
||||
from frappe import _
|
||||
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
|
||||
from erpnext.accounts.report.general_ledger.general_ledger import execute
|
||||
from frappe.utils.dashboard import cache_source, get_from_date_from_timespan
|
||||
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
|
||||
|
||||
from frappe.utils.dashboard import cache_source
|
||||
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
|
||||
from frappe.utils.nestedset import get_descendants_of
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -137,6 +137,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
"cost_center": erpnext.get_default_cost_center(self.company)
|
||||
})
|
||||
|
||||
if self.bank_charges:
|
||||
je.append("accounts", {
|
||||
"account": self.bank_charges_account,
|
||||
"debit_in_account_currency": flt(self.bank_charges),
|
||||
|
@ -80,6 +80,7 @@ class TestInvoiceDiscounting(unittest.TestCase):
|
||||
short_term_loan=self.short_term_loan,
|
||||
bank_charges_account=self.bank_charges_account,
|
||||
bank_account=self.bank_account,
|
||||
bank_charges=100
|
||||
)
|
||||
|
||||
je = inv_disc.create_disbursement_entry()
|
||||
@ -289,6 +290,7 @@ def create_invoice_discounting(invoices, **args):
|
||||
inv_disc.bank_account=args.bank_account
|
||||
inv_disc.loan_start_date = args.start or nowdate()
|
||||
inv_disc.loan_period = args.period or 30
|
||||
inv_disc.bank_charges = flt(args.bank_charges)
|
||||
|
||||
for d in invoices:
|
||||
inv_disc.append("invoices", {
|
||||
|
@ -34,6 +34,7 @@ class JournalEntry(AccountsController):
|
||||
self.validate_entries_for_advance()
|
||||
self.validate_multi_currency()
|
||||
self.set_amounts_in_company_currency()
|
||||
self.validate_debit_credit_amount()
|
||||
self.validate_total_debit_and_credit()
|
||||
self.validate_against_jv()
|
||||
self.validate_reference_doc()
|
||||
@ -339,8 +340,7 @@ class JournalEntry(AccountsController):
|
||||
currency=account_currency)
|
||||
|
||||
if flt(voucher_total) < (flt(order.advance_paid) + total):
|
||||
frappe.throw(_("Advance paid against {0} {1} cannot be greater \
|
||||
than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
|
||||
frappe.throw(_("Advance paid against {0} {1} cannot be greater than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
|
||||
|
||||
def validate_invoices(self):
|
||||
"""Validate totals and docstatus for invoices"""
|
||||
@ -369,6 +369,11 @@ class JournalEntry(AccountsController):
|
||||
if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
|
||||
if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
|
||||
|
||||
def validate_debit_credit_amount(self):
|
||||
for d in self.get('accounts'):
|
||||
if not flt(d.debit) and not flt(d.credit):
|
||||
frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx))
|
||||
|
||||
def validate_total_debit_and_credit(self):
|
||||
self.set_total_debit_credit()
|
||||
if self.difference:
|
||||
|
@ -35,6 +35,15 @@ frappe.ui.form.on('POS Profile', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("taxes_and_charges", function() {
|
||||
return {
|
||||
filters: [
|
||||
['Sales Taxes and Charges Template', 'company', '=', frm.doc.company],
|
||||
['Sales Taxes and Charges Template', 'docstatus', '!=', 2]
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('company_address', function(doc) {
|
||||
if(!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
|
@ -1,5 +1,7 @@
|
||||
import traceback
|
||||
|
||||
import taxjar
|
||||
|
||||
import frappe
|
||||
from erpnext import get_default_company
|
||||
from frappe import _
|
||||
@ -29,7 +31,6 @@ def get_client():
|
||||
|
||||
|
||||
def create_transaction(doc, method):
|
||||
import taxjar
|
||||
"""Create an order transaction in TaxJar"""
|
||||
|
||||
if not TAXJAR_CREATE_TRANSACTIONS:
|
||||
|
@ -85,8 +85,7 @@ frappe.ui.form.on('Clinical Procedure', {
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frappe.show_alert({
|
||||
message: __('Stock Entry {0} created',
|
||||
['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
|
||||
message: __('Stock Entry {0} created', ['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
|
||||
indicator: 'green'
|
||||
});
|
||||
}
|
||||
@ -105,8 +104,7 @@ frappe.ui.form.on('Clinical Procedure', {
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
if (r.message == 'insufficient stock') {
|
||||
let msg = __('Stock quantity to start the Procedure is not available in the Warehouse {0}. Do you want to record a Stock Entry?',
|
||||
[frm.doc.warehouse.bold()]);
|
||||
let msg = __('Stock quantity to start the Procedure is not available in the Warehouse {0}. Do you want to record a Stock Entry?', [frm.doc.warehouse.bold()]);
|
||||
frappe.confirm(
|
||||
msg,
|
||||
function() {
|
||||
|
@ -352,14 +352,16 @@ scheduler_events = {
|
||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
|
||||
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
|
||||
"erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.automatically_allocate_leaves_based_on_leave_policy",
|
||||
"erpnext.hr.utils.generate_leave_encashment",
|
||||
"erpnext.hr.utils.allocate_earned_leaves",
|
||||
"erpnext.hr.utils.grant_leaves_automatically",
|
||||
"erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall.create_process_loan_security_shortfall",
|
||||
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
||||
"erpnext.crm.doctype.lead.lead.daily_open_lead"
|
||||
],
|
||||
"monthly_long": [
|
||||
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
|
||||
"erpnext.hr.utils.allocate_earned_leaves",
|
||||
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_demand_loans"
|
||||
]
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -1,167 +1,69 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"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",
|
||||
"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
|
||||
}
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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"))
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,10 +411,16 @@ 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.delete_doc_if_exists("Leave Type", 'Test Earned Leave Type', force=1)
|
||||
frappe.get_doc(dict(
|
||||
leave_type_name = leave_type,
|
||||
doctype = 'Leave Type',
|
||||
@ -422,13 +429,21 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
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
|
||||
|
@ -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,22 @@ 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 test_leave_balance_value_and_amount(self):
|
||||
frappe.db.sql('''delete from `tabLeave Encashment`''')
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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
|
@ -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',
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
});
|
@ -0,0 +1,160 @@
|
||||
{
|
||||
"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-10-15 15:18:15.227848",
|
||||
"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,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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"))
|
||||
|
@ -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
|
@ -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,26 +287,36 @@ 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:
|
||||
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):
|
||||
|
||||
if not allocation.leave_policy_assignment and not allocation.leave_policy:
|
||||
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'])
|
||||
|
||||
from_date=allocation.from_date
|
||||
|
||||
if e_leave_type.based_on_date_of_joining_date:
|
||||
from_date = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
|
||||
|
||||
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":
|
||||
@ -333,10 +330,25 @@ def allocate_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:
|
||||
continue
|
||||
if new_allocation != allocation.total_leaves_allocated:
|
||||
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
|
||||
create_additional_leave_ledger_entry(allocation, earned_leaves, today)
|
||||
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()
|
||||
|
@ -735,4 +735,5 @@ erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
|
||||
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||
erpnext.patches.v13_0.update_custom_fields_for_shopify
|
||||
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
||||
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
||||
erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
|
@ -0,0 +1,77 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
if "leave_policy" in frappe.db.get_table_columns("Employee"):
|
||||
employees_with_leave_policy = frappe.db.sql("SELECT name, leave_policy FROM `tabEmployee` WHERE leave_policy IS NOT NULL and leave_policy != ''", as_dict = 1)
|
||||
|
||||
employee_with_assignment = []
|
||||
leave_policy =[]
|
||||
|
||||
#for employee
|
||||
|
||||
for employee in employees_with_leave_policy:
|
||||
alloc = frappe.db.exists("Leave Allocation", {"employee":employee.name, "leave_policy": employee.leave_policy, "docstatus": 1})
|
||||
if not alloc:
|
||||
create_assignment(employee.name, employee.leave_policy)
|
||||
|
||||
employee_with_assignment.append(employee.name)
|
||||
leave_policy.append(employee.leave_policy)
|
||||
|
||||
|
||||
if "default_leave_policy" in frappe.db.get_table_columns("Employee"):
|
||||
employee_grade_with_leave_policy = frappe.db.sql("SELECT name, default_leave_policy FROM `tabEmployee Grade` WHERE default_leave_policy IS NOT NULL and default_leave_policy!=''", as_dict = 1)
|
||||
|
||||
#for whole employee Grade
|
||||
|
||||
for grade in employee_grade_with_leave_policy:
|
||||
employees = get_employee_with_grade(grade.name)
|
||||
for employee in employees:
|
||||
|
||||
if employee not in employee_with_assignment: #Will ensure no duplicate
|
||||
alloc = frappe.db.exists("Leave Allocation", {"employee":employee.name, "leave_policy": grade.default_leave_policy, "docstatus": 1})
|
||||
if not alloc:
|
||||
create_assignment(employee.name, grade.default_leave_policy)
|
||||
leave_policy.append(grade.default_leave_policy)
|
||||
|
||||
#for old Leave allocation and leave policy from allocation, which may got updated in employee grade.
|
||||
leave_allocations = frappe.db.sql("SELECT leave_policy, leave_period, employee FROM `tabLeave Allocation` WHERE leave_policy IS NOT NULL and leave_policy != '' and docstatus = 1 ", as_dict = 1)
|
||||
|
||||
for allocation in leave_allocations:
|
||||
if allocation.leave_policy not in leave_policy:
|
||||
create_assignment(allocation.employee, allocation.leave_policy, leave_period=allocation.leave_period,
|
||||
allocation_exists=True)
|
||||
|
||||
def create_assignment(employee, leave_policy, leave_period=None, allocation_exists = False):
|
||||
|
||||
filters = {"employee":employee, "leave_policy": leave_policy}
|
||||
if leave_period:
|
||||
filters["leave_period"] = leave_period
|
||||
|
||||
if not frappe.db.exists("Leave Policy Assignment" , filters):
|
||||
lpa = frappe.new_doc("Leave Policy Assignment")
|
||||
lpa.employee = employee
|
||||
lpa.leave_policy = leave_policy
|
||||
|
||||
lpa.flags.ignore_mandatory = True
|
||||
if allocation_exists:
|
||||
lpa.assignment_based_on = 'Leave Period'
|
||||
lpa.leave_period = leave_period
|
||||
lpa.leaves_allocated = 1
|
||||
|
||||
lpa.save()
|
||||
if allocation_exists:
|
||||
lpa.submit()
|
||||
#Updating old Leave Allocation
|
||||
frappe.db.sql("Update `tabLeave Allocation` set leave_policy_assignment = %s", lpa.name)
|
||||
|
||||
|
||||
def get_employee_with_grade(grade):
|
||||
return frappe.get_list("Employee", filters = {"grade": grade})
|
||||
|
||||
|
||||
|
@ -13,12 +13,12 @@ frappe.ui.form.on("Salary Slip", {
|
||||
];
|
||||
});
|
||||
|
||||
frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(){
|
||||
frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
employee: frm.doc.employee
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
frm.set_query("salary_component", "earnings", function() {
|
||||
@ -26,7 +26,7 @@ frappe.ui.form.on("Salary Slip", {
|
||||
filters: {
|
||||
type: "earning"
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("salary_component", "deductions", function() {
|
||||
@ -34,18 +34,18 @@ frappe.ui.form.on("Salary Slip", {
|
||||
filters: {
|
||||
type: "deduction"
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("employee", function() {
|
||||
return{
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query"
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
start_date: function(frm){
|
||||
if(frm.doc.start_date){
|
||||
start_date: function(frm) {
|
||||
if (frm.doc.start_date) {
|
||||
frm.trigger("set_end_date");
|
||||
}
|
||||
},
|
||||
@ -54,7 +54,7 @@ frappe.ui.form.on("Salary Slip", {
|
||||
frm.events.get_emp_and_working_day_details(frm);
|
||||
},
|
||||
|
||||
set_end_date: function(frm){
|
||||
set_end_date: function(frm) {
|
||||
frappe.call({
|
||||
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
|
||||
args: {
|
||||
@ -66,22 +66,22 @@ frappe.ui.form.on("Salary Slip", {
|
||||
frm.set_value('end_date', r.message.end_date);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
var company = locals[':Company'][frm.doc.company];
|
||||
if(!frm.doc.letter_head && company.default_letter_head) {
|
||||
if (!frm.doc.letter_head && company.default_letter_head) {
|
||||
frm.set_value('letter_head', company.default_letter_head);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.trigger("toggle_fields")
|
||||
frm.trigger("toggle_fields");
|
||||
|
||||
var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
|
||||
cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields,false);
|
||||
cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields,false);
|
||||
cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false);
|
||||
cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
|
||||
},
|
||||
|
||||
salary_slip_based_on_timesheet: function(frm) {
|
||||
@ -98,12 +98,12 @@ frappe.ui.form.on("Salary Slip", {
|
||||
frm.events.get_emp_and_working_day_details(frm);
|
||||
},
|
||||
|
||||
leave_without_pay: function(frm){
|
||||
leave_without_pay: function(frm) {
|
||||
if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
|
||||
return frappe.call({
|
||||
method: 'process_salary_based_on_working_days',
|
||||
doc: frm.doc,
|
||||
callback: function(r, rt) {
|
||||
callback: function() {
|
||||
frm.refresh();
|
||||
}
|
||||
});
|
||||
@ -121,10 +121,10 @@ frappe.ui.form.on("Salary Slip", {
|
||||
return frappe.call({
|
||||
method: 'get_emp_and_working_day_details',
|
||||
doc: frm.doc,
|
||||
callback: function(r, rt) {
|
||||
callback: function(r) {
|
||||
frm.refresh();
|
||||
if (r.message){
|
||||
frm.fields_dict.absent_days.set_description("Unmarked Days is treated as "+ r.message +". You can can change this in " + frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true));
|
||||
if (r.message[1] !== "Leave" && r.message[0]) {
|
||||
frm.fields_dict.absent_days.set_description(__("Unmarked Days is treated as ")+ r.message[0] +__(". You can can change this in ") + frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -141,7 +141,7 @@ frappe.ui.form.on('Salary Slip Timesheet', {
|
||||
});
|
||||
|
||||
// calculate total working hours, earnings based on hourly wages and totals
|
||||
var total_work_hours = function(frm, dt, dn) {
|
||||
var total_work_hours = function(frm) {
|
||||
var total_working_hours = 0.0;
|
||||
$.each(frm.doc["timesheets"] || [], function(i, timesheet) {
|
||||
total_working_hours += timesheet.working_hours;
|
||||
@ -165,4 +165,4 @@ var total_work_hours = function(frm, dt, dn) {
|
||||
frm.doc.rounded_total = Math.round(frm.doc.net_pay);
|
||||
refresh_many(['net_pay', 'rounded_total']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -136,8 +136,8 @@ class SalarySlip(TransactionBase):
|
||||
self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
|
||||
self.set_time_sheet()
|
||||
self.pull_sal_struct()
|
||||
consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, "consider_unmarked_attendance_as") or "Present"
|
||||
return consider_unmarked_attendance_as
|
||||
payroll_based_on, consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"])
|
||||
return [payroll_based_on, consider_unmarked_attendance_as]
|
||||
|
||||
def set_time_sheet(self):
|
||||
if self.salary_slip_based_on_timesheet:
|
||||
@ -210,10 +210,10 @@ class SalarySlip(TransactionBase):
|
||||
frappe.throw(_("Please set Payroll based on in Payroll settings"))
|
||||
|
||||
if payroll_based_on == "Attendance":
|
||||
actual_lwp, absent = self.calculate_lwp_and_absent_days_based_on_attendance(holidays)
|
||||
actual_lwp, absent = self.calculate_lwp_ppl_and_absent_days_based_on_attendance(holidays)
|
||||
self.absent_days = absent
|
||||
else:
|
||||
actual_lwp = self.calculate_lwp_based_on_leave_application(holidays, working_days)
|
||||
actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(holidays, working_days)
|
||||
|
||||
if not lwp:
|
||||
lwp = actual_lwp
|
||||
@ -300,7 +300,7 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
return holidays
|
||||
|
||||
def calculate_lwp_based_on_leave_application(self, holidays, working_days):
|
||||
def calculate_lwp_or_ppl_based_on_leave_application(self, holidays, working_days):
|
||||
lwp = 0
|
||||
holidays = "','".join(holidays)
|
||||
daily_wages_fraction_for_half_day = \
|
||||
@ -311,10 +311,12 @@ class SalarySlip(TransactionBase):
|
||||
leave = frappe.db.sql("""
|
||||
SELECT t1.name,
|
||||
CASE WHEN (t1.half_day_date = %(dt)s or t1.to_date = t1.from_date)
|
||||
THEN t1.half_day else 0 END
|
||||
THEN t1.half_day else 0 END,
|
||||
t2.is_ppl,
|
||||
t2.fraction_of_daily_salary_per_leave
|
||||
FROM `tabLeave Application` t1, `tabLeave Type` t2
|
||||
WHERE t2.name = t1.leave_type
|
||||
AND t2.is_lwp = 1
|
||||
AND (t2.is_lwp = 1 or t2.is_ppl = 1)
|
||||
AND t1.docstatus = 1
|
||||
AND t1.employee = %(employee)s
|
||||
AND ifnull(t1.salary_slip, '') = ''
|
||||
@ -327,19 +329,35 @@ class SalarySlip(TransactionBase):
|
||||
""".format(holidays), {"employee": self.employee, "dt": dt})
|
||||
|
||||
if leave:
|
||||
equivalent_lwp_count = 0
|
||||
is_half_day_leave = cint(leave[0][1])
|
||||
lwp += (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
|
||||
is_partially_paid_leave = cint(leave[0][2])
|
||||
fraction_of_daily_salary_per_leave = flt(leave[0][3])
|
||||
|
||||
equivalent_lwp_count = (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
|
||||
|
||||
if is_partially_paid_leave:
|
||||
equivalent_lwp_count *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
|
||||
|
||||
lwp += equivalent_lwp_count
|
||||
|
||||
return lwp
|
||||
|
||||
def calculate_lwp_and_absent_days_based_on_attendance(self, holidays):
|
||||
def calculate_lwp_ppl_and_absent_days_based_on_attendance(self, holidays):
|
||||
lwp = 0
|
||||
absent = 0
|
||||
|
||||
daily_wages_fraction_for_half_day = \
|
||||
flt(frappe.db.get_value("Payroll Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
|
||||
|
||||
lwp_leave_types = dict(frappe.get_all("Leave Type", {"is_lwp": 1}, ["name", "include_holiday"], as_list=1))
|
||||
leave_types = frappe.get_all("Leave Type",
|
||||
or_filters=[["is_ppl", "=", 1], ["is_lwp", "=", 1]],
|
||||
fields =["name", "is_lwp", "is_ppl", "fraction_of_daily_salary_per_leave", "include_holiday"])
|
||||
|
||||
leave_type_map = {}
|
||||
for leave_type in leave_types:
|
||||
leave_type_map[leave_type.name] = leave_type
|
||||
|
||||
attendances = frappe.db.sql('''
|
||||
SELECT attendance_date, status, leave_type
|
||||
FROM `tabAttendance`
|
||||
@ -351,21 +369,30 @@ class SalarySlip(TransactionBase):
|
||||
''', values=(self.employee, self.start_date, self.end_date), as_dict=1)
|
||||
|
||||
for d in attendances:
|
||||
if d.status in ('Half Day', 'On Leave') and d.leave_type and d.leave_type not in lwp_leave_types:
|
||||
if d.status in ('Half Day', 'On Leave') and d.leave_type and d.leave_type not in leave_type_map.keys():
|
||||
continue
|
||||
|
||||
if formatdate(d.attendance_date, "yyyy-mm-dd") in holidays:
|
||||
if d.status == "Absent" or \
|
||||
(d.leave_type and d.leave_type in lwp_leave_types and not lwp_leave_types[d.leave_type]):
|
||||
(d.leave_type and d.leave_type in leave_type_map.keys() and not leave_type_map[d.leave_type]['include_holiday']):
|
||||
continue
|
||||
|
||||
if d.leave_type:
|
||||
fraction_of_daily_salary_per_leave = leave_type_map[d.leave_type]["fraction_of_daily_salary_per_leave"]
|
||||
|
||||
if d.status == "Half Day":
|
||||
lwp += (1 - daily_wages_fraction_for_half_day)
|
||||
elif d.status == "On Leave" and d.leave_type in lwp_leave_types:
|
||||
lwp += 1
|
||||
equivalent_lwp = (1 - daily_wages_fraction_for_half_day)
|
||||
|
||||
if d.leave_type in leave_type_map.keys() and leave_type_map[d.leave_type]["is_ppl"]:
|
||||
equivalent_lwp *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
|
||||
lwp += equivalent_lwp
|
||||
elif d.status == "On Leave" and d.leave_type and d.leave_type in leave_type_map.keys():
|
||||
equivalent_lwp = 1
|
||||
if leave_type_map[d.leave_type]["is_ppl"]:
|
||||
equivalent_lwp *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
|
||||
lwp += equivalent_lwp
|
||||
elif d.status == "Absent":
|
||||
absent += 1
|
||||
|
||||
return lwp, absent
|
||||
|
||||
def add_earning_for_hourly_wages(self, doc, salary_component, amount):
|
||||
@ -949,9 +976,8 @@ class SalarySlip(TransactionBase):
|
||||
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
||||
total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
|
||||
if payment.total_payment > total_amount:
|
||||
frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2}
|
||||
against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment),
|
||||
frappe.bold(total_amount), frappe.bold(payment.loan)))
|
||||
frappe.throw(_("Row {0}: Paid amount {1} is greater than pending accrued amount {2}against loan {3}").format(
|
||||
payment.idx, frappe.bold(payment.total_payment),frappe.bold(total_amount), frappe.bold(payment.loan)))
|
||||
|
||||
self.total_interest_amount += payment.interest_amount
|
||||
self.total_principal_amount += payment.principal_amount
|
||||
|
@ -13,6 +13,8 @@ from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_
|
||||
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
|
||||
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
|
||||
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
||||
from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration \
|
||||
import create_payroll_period, create_exemption_category
|
||||
|
||||
@ -93,14 +95,27 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
make_leave_application(emp_id, first_sunday, add_days(first_sunday, 3), "Leave Without Pay")
|
||||
|
||||
leave_type_ppl = create_leave_type(leave_type_name="Test Partially Paid Leave", is_ppl = 1)
|
||||
leave_type_ppl.save()
|
||||
|
||||
alloc = create_leave_allocation(
|
||||
employee = emp_id, from_date = add_days(first_sunday, 4),
|
||||
to_date = add_days(first_sunday, 10), new_leaves_allocated = 3,
|
||||
leave_type = "Test Partially Paid Leave")
|
||||
alloc.save()
|
||||
alloc.submit()
|
||||
|
||||
#two day leave ppl with fraction_of_daily_salary_per_leave = 0.5 equivalent to single day lwp
|
||||
make_leave_application(emp_id, add_days(first_sunday, 4), add_days(first_sunday, 5), "Test Partially Paid Leave")
|
||||
|
||||
ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
|
||||
|
||||
self.assertEqual(ss.leave_without_pay, 3)
|
||||
self.assertEqual(ss.leave_without_pay, 4)
|
||||
|
||||
days_in_month = no_of_days[0]
|
||||
no_of_holidays = no_of_days[1]
|
||||
|
||||
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 3)
|
||||
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 4)
|
||||
|
||||
#Gross pay calculation based on attendances
|
||||
gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay))
|
||||
|
@ -218,8 +218,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
||||
var is_negative_qty = false;
|
||||
for(var i = 0; i<fieldnames.length; i++) {
|
||||
if(item[fieldnames[i]] < 0){
|
||||
frappe.msgprint(__("Row #{0}: {1} can not be negative for item {2}",
|
||||
[item.idx,__(frappe.meta.get_label(cdt, fieldnames[i], cdn)), item.item_code]));
|
||||
frappe.msgprint(__("Row #{0}: {1} can not be negative for item {2}", [item.idx,__(frappe.meta.get_label(cdt, fieldnames[i], cdn)), item.item_code]));
|
||||
is_negative_qty = true;
|
||||
break;
|
||||
}
|
||||
|
@ -209,6 +209,17 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
});
|
||||
}
|
||||
|
||||
if (this.frm.fields_dict.taxes_and_charges) {
|
||||
this.frm.set_query("taxes_and_charges", function() {
|
||||
return {
|
||||
filters: [
|
||||
['company', '=', me.frm.doc.company],
|
||||
['docstatus', '!=', 2]
|
||||
]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
onload: function() {
|
||||
var me = this;
|
||||
|
@ -326,8 +326,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frappe.msgprint({
|
||||
message: __('Work Orders Created: {0}',
|
||||
[r.message.map(function(d) {
|
||||
message: __('Work Orders Created: {0}', [r.message.map(function(d) {
|
||||
return repl('<a href="#Form/Work Order/%(name)s">%(name)s</a>', {name:d})
|
||||
}).join(', ')]),
|
||||
indicator: 'green'
|
||||
|
@ -644,8 +644,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
})
|
||||
} else if (available_qty < qty_needed) {
|
||||
frappe.show_alert({
|
||||
message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.',
|
||||
[bold_item_code, bold_warehouse, bold_available_qty]),
|
||||
message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
|
||||
indicator: 'orange'
|
||||
});
|
||||
frappe.utils.play_sound("error");
|
||||
|
@ -42,16 +42,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
|
||||
me.frm.set_query('customer_address', erpnext.queries.address_query);
|
||||
me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
|
||||
|
||||
if(this.frm.fields_dict.taxes_and_charges) {
|
||||
this.frm.set_query("taxes_and_charges", function() {
|
||||
return {
|
||||
filters: [
|
||||
['Sales Taxes and Charges Template', 'company', '=', me.frm.doc.company],
|
||||
['Sales Taxes and Charges Template', 'docstatus', '!=', 2]
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict.selling_price_list) {
|
||||
this.frm.set_query("selling_price_list", function() {
|
||||
@ -479,7 +469,7 @@ frappe.ui.form.on(cur_frm.doctype,"project", function(frm) {
|
||||
$.each(frm.doc["items"] || [], function(i, row) {
|
||||
if(r.message) {
|
||||
frappe.model.set_value(row.doctype, row.name, "cost_center", r.message);
|
||||
frappe.msgprint(__("Cost Center For Item with Item Code '"+row.item_name+"' has been Changed to "+ r.message));
|
||||
frappe.msgprint(__("Cost Center For Item with Item Code {0} has been Changed to {1}", [row.item_name, r.message]));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ frappe.ui.form.on('Sales Person', {
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.__onload && frm.doc.__onload.dashboard_info) {
|
||||
var info = frm.doc.__onload.dashboard_info;
|
||||
frm.dashboard.add_indicator(__('Total Contribution Amount: {0}',
|
||||
[format_currency(info.allocated_amount, info.currency)]), 'blue');
|
||||
frm.dashboard.add_indicator(__('Total Contribution Amount: {0}', [format_currency(info.allocated_amount, info.currency)]), 'blue');
|
||||
}
|
||||
},
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user