Merge branch 'develop' into version-12
This commit is contained in:
commit
7ff82fccf3
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '12.0.7'
|
||||
__version__ = '12.0.8'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
@ -123,7 +123,8 @@ frappe.treeview_settings["Account"] = {
|
||||
if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){
|
||||
|
||||
// show Dr if positive since balance is calculated as debit - credit else show Cr
|
||||
let dr_or_cr = node.data.balance_in_account_currency > 0 ? "Dr": "Cr";
|
||||
let balance = node.data.balance_in_account_currency || node.data.balance;
|
||||
let dr_or_cr = balance > 0 ? "Dr": "Cr";
|
||||
|
||||
if (node.data && node.data.balance!==undefined) {
|
||||
$('<span class="balance-area pull-right text-muted small">'
|
||||
|
@ -17,8 +17,7 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_document",
|
||||
@ -34,8 +33,7 @@
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Default Dimension",
|
||||
"options": "reference_document",
|
||||
"reqd": 1
|
||||
"options": "reference_document"
|
||||
},
|
||||
{
|
||||
"columns": 3,
|
||||
@ -55,7 +53,7 @@
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-07-17 23:34:33.026883",
|
||||
"modified": "2019-08-15 11:59:09.389891",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting Dimension Detail",
|
||||
|
@ -50,7 +50,7 @@ class BankTransaction(StatusUpdater):
|
||||
if paid_amount and allocated_amount:
|
||||
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
|
||||
frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount))))
|
||||
elif flt(allocated_amount[0]["allocated_amount"]) == flt(paid_amount):
|
||||
else:
|
||||
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
|
||||
self.clear_simple_entry(payment_entry)
|
||||
|
||||
|
@ -624,8 +624,8 @@ def get_outstanding_reference_documents(args):
|
||||
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
|
||||
|
||||
if not data:
|
||||
frappe.msgprint(_("No outstanding invoices found for the {0} <b>{1}</b> which qualify the filters you have specified")
|
||||
.format(args.get("party_type").lower(), args.get("party")))
|
||||
frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
|
||||
.format(args.get("party_type").lower(), frappe.bold(args.get("party"))))
|
||||
|
||||
return data
|
||||
|
||||
@ -683,8 +683,8 @@ def get_orders_to_be_billed(posting_date, party_type, party,
|
||||
|
||||
order_list = []
|
||||
for d in orders:
|
||||
if not (d.outstanding_amount >= filters.get("outstanding_amt_greater_than")
|
||||
and d.outstanding_amount <= filters.get("outstanding_amt_less_than")):
|
||||
if not (flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
|
||||
and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))):
|
||||
continue
|
||||
|
||||
d["voucher_type"] = voucher_type
|
||||
@ -761,9 +761,23 @@ def get_party_details(company, party_type, party, date, cost_center=None):
|
||||
@frappe.whitelist()
|
||||
def get_account_details(account, date, cost_center=None):
|
||||
frappe.has_permission('Payment Entry', throw=True)
|
||||
|
||||
# to check if the passed account is accessible under reference doctype Payment Entry
|
||||
account_list = frappe.get_list('Account', {
|
||||
'name': account
|
||||
}, reference_doctype='Payment Entry', limit=1)
|
||||
|
||||
# There might be some user permissions which will allow account under certain doctypes
|
||||
# except for Payment Entry, only in such case we should throw permission error
|
||||
if not account_list:
|
||||
frappe.throw(_('Account: {0} is not permitted under Payment Entry').format(account))
|
||||
|
||||
account_balance = get_balance_on(account, date, cost_center=cost_center,
|
||||
ignore_account_permission=True)
|
||||
|
||||
return frappe._dict({
|
||||
"account_currency": get_account_currency(account),
|
||||
"account_balance": get_balance_on(account, date, cost_center=cost_center),
|
||||
"account_balance": account_balance,
|
||||
"account_type": frappe.db.get_value("Account", account, "account_type")
|
||||
})
|
||||
|
||||
|
@ -257,11 +257,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
|
||||
voucher_type = ('Credit Note'
|
||||
if d.voucher_type == 'Sales Invoice' else 'Debit Note')
|
||||
|
||||
dr_or_cr = ('credit_in_account_currency'
|
||||
if d.reference_type == 'Sales Invoice' else 'debit_in_account_currency')
|
||||
|
||||
reconcile_dr_or_cr = ('debit_in_account_currency'
|
||||
if dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
|
||||
if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
|
||||
|
||||
jv = frappe.get_doc({
|
||||
"doctype": "Journal Entry",
|
||||
@ -272,8 +269,7 @@ def reconcile_dr_cr_note(dr_cr_notes):
|
||||
'account': d.account,
|
||||
'party': d.party,
|
||||
'party_type': d.party_type,
|
||||
reconcile_dr_or_cr: (abs(d.allocated_amount)
|
||||
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
|
||||
d.dr_or_cr: abs(d.allocated_amount),
|
||||
'reference_type': d.against_voucher_type,
|
||||
'reference_name': d.against_voucher
|
||||
},
|
||||
@ -281,7 +277,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
|
||||
'account': d.account,
|
||||
'party': d.party,
|
||||
'party_type': d.party_type,
|
||||
dr_or_cr: abs(d.allocated_amount),
|
||||
reconcile_dr_or_cr: (abs(d.allocated_amount)
|
||||
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
|
||||
'reference_type': d.voucher_type,
|
||||
'reference_name': d.voucher_no
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ def get_item_tax_data():
|
||||
# example: {'Consulting Services': {'Excise 12 - TS': '12.000'}}
|
||||
|
||||
itemwise_tax = {}
|
||||
taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax`""", as_dict=1)
|
||||
taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax Template Detail`""", as_dict=1)
|
||||
|
||||
for tax in taxes:
|
||||
if tax.parent not in itemwise_tax:
|
||||
|
@ -469,7 +469,9 @@ def get_timeline_data(doctype, name):
|
||||
# fetch and append data from Activity Log
|
||||
data += frappe.db.sql("""select {fields}
|
||||
from `tabActivity Log`
|
||||
where reference_doctype={doctype} and reference_name={name}
|
||||
where (reference_doctype="{doctype}" and reference_name="{name}")
|
||||
or (timeline_doctype in ("{doctype}") and timeline_name="{name}")
|
||||
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name="{name}")
|
||||
and status!='Success' and creation > {after}
|
||||
{group_by} order by creation desc
|
||||
""".format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields,
|
||||
|
@ -33,6 +33,9 @@ class ReceivablePayableReport(object):
|
||||
|
||||
columns += [_(args.get("party_type")) + ":Link/" + args.get("party_type") + ":200"]
|
||||
|
||||
if party_naming_by == "Naming Series":
|
||||
columns += [args.get("party_type") + " Name::110"]
|
||||
|
||||
if args.get("party_type") == 'Customer':
|
||||
columns.append({
|
||||
"label": _("Customer Contact"),
|
||||
@ -42,9 +45,6 @@ class ReceivablePayableReport(object):
|
||||
"width": 100
|
||||
})
|
||||
|
||||
if party_naming_by == "Naming Series":
|
||||
columns += [args.get("party_type") + " Name::110"]
|
||||
|
||||
columns.append({
|
||||
"label": _("Voucher Type"),
|
||||
"fieldtype": "Data",
|
||||
@ -197,8 +197,10 @@ class ReceivablePayableReport(object):
|
||||
if self.filters.based_on_payment_terms and gl_entries_data:
|
||||
self.payment_term_map = self.get_payment_term_detail(voucher_nos)
|
||||
|
||||
self.gle_inclusion_map = {}
|
||||
for gle in gl_entries_data:
|
||||
if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers, return_entries):
|
||||
self.gle_inclusion_map[gle.name] = True
|
||||
outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount(
|
||||
gle,self.filters.report_date, self.dr_or_cr, return_entries)
|
||||
temp_outstanding_amt = outstanding_amount
|
||||
@ -409,7 +411,9 @@ class ReceivablePayableReport(object):
|
||||
for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no):
|
||||
if getdate(e.posting_date) <= report_date \
|
||||
and (e.name!=gle.name or (e.voucher_no in return_entries and not return_entries.get(e.voucher_no))):
|
||||
|
||||
if e.name!=gle.name and self.gle_inclusion_map.get(e.name):
|
||||
continue
|
||||
self.gle_inclusion_map[e.name] = True
|
||||
amount = flt(e.get(reverse_dr_or_cr), self.currency_precision) - flt(e.get(dr_or_cr), self.currency_precision)
|
||||
if e.voucher_no not in return_entries:
|
||||
payment_amount += amount
|
||||
|
@ -425,9 +425,12 @@ def get_cost_centers_with_children(cost_centers):
|
||||
|
||||
all_cost_centers = []
|
||||
for d in cost_centers:
|
||||
lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"])
|
||||
children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
|
||||
all_cost_centers += [c.name for c in children]
|
||||
if frappe.db.exists("Cost Center", d):
|
||||
lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"])
|
||||
children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
|
||||
all_cost_centers += [c.name for c in children]
|
||||
else:
|
||||
frappe.throw(_("Cost Center: {0} does not exist".format(d)))
|
||||
|
||||
return list(set(all_cost_centers))
|
||||
|
||||
|
@ -84,7 +84,8 @@ def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None):
|
||||
throw(_("{0} '{1}' not in Fiscal Year {2}").format(label, formatdate(date), fiscal_year))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance_on(account=None, date=None, party_type=None, party=None, company=None, in_account_currency=True, cost_center=None):
|
||||
def get_balance_on(account=None, date=None, party_type=None, party=None, company=None,
|
||||
in_account_currency=True, cost_center=None, ignore_account_permission=False):
|
||||
if not account and frappe.form_dict.get("account"):
|
||||
account = frappe.form_dict.get("account")
|
||||
if not date and frappe.form_dict.get("date"):
|
||||
@ -140,7 +141,8 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
|
||||
|
||||
if account:
|
||||
|
||||
if not frappe.flags.ignore_account_permission:
|
||||
if not (frappe.flags.ignore_account_permission
|
||||
or ignore_account_permission):
|
||||
acc.check_permission("read")
|
||||
|
||||
if report_type == 'Profit and Loss':
|
||||
|
@ -8,12 +8,18 @@
|
||||
"from",
|
||||
"to",
|
||||
"column_break_3",
|
||||
"received_by",
|
||||
"medium",
|
||||
"caller_information",
|
||||
"contact",
|
||||
"contact_name",
|
||||
"column_break_10",
|
||||
"lead",
|
||||
"lead_name",
|
||||
"section_break_5",
|
||||
"status",
|
||||
"duration",
|
||||
"recording_url",
|
||||
"summary"
|
||||
"recording_url"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -60,12 +66,6 @@
|
||||
"label": "Duration",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "summary",
|
||||
"fieldtype": "Data",
|
||||
"label": "Summary",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "recording_url",
|
||||
"fieldtype": "Data",
|
||||
@ -77,10 +77,58 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Medium",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "received_by",
|
||||
"fieldtype": "Link",
|
||||
"label": "Received By",
|
||||
"options": "Employee",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "caller_information",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Caller Information"
|
||||
},
|
||||
{
|
||||
"fieldname": "contact",
|
||||
"fieldtype": "Link",
|
||||
"label": "Contact",
|
||||
"options": "Contact",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "lead",
|
||||
"fieldtype": "Link",
|
||||
"label": "Lead ",
|
||||
"options": "Lead",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "contact.name",
|
||||
"fieldname": "contact_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Contact Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_10",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "lead.lead_name",
|
||||
"fieldname": "lead_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Lead Name",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"modified": "2019-07-01 09:09:48.516722",
|
||||
"modified": "2019-08-06 05:46:53.144683",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Communication",
|
||||
"name": "Call Log",
|
||||
@ -97,10 +145,15 @@
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Employee"
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"title_field": "from",
|
||||
"track_changes": 1
|
||||
"track_changes": 1,
|
||||
"track_views": 1
|
||||
}
|
@ -4,16 +4,88 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.crm.doctype.utils import get_employee_emails_for_popup
|
||||
from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup, strip_number
|
||||
from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number
|
||||
from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number
|
||||
|
||||
class CallLog(Document):
|
||||
def before_insert(self):
|
||||
number = strip_number(self.get('from'))
|
||||
self.contact = get_contact_with_phone_number(number)
|
||||
self.lead = get_lead_with_phone_number(number)
|
||||
|
||||
def after_insert(self):
|
||||
employee_emails = get_employee_emails_for_popup(self.medium)
|
||||
for email in employee_emails:
|
||||
frappe.publish_realtime('show_call_popup', self, user=email)
|
||||
self.trigger_call_popup()
|
||||
|
||||
def on_update(self):
|
||||
doc_before_save = self.get_doc_before_save()
|
||||
if doc_before_save and doc_before_save.status in ['Ringing'] and self.status in ['Missed', 'Completed']:
|
||||
if not doc_before_save: return
|
||||
if doc_before_save.status in ['Ringing'] and self.status in ['Missed', 'Completed']:
|
||||
frappe.publish_realtime('call_{id}_disconnected'.format(id=self.id), self)
|
||||
elif doc_before_save.to != self.to:
|
||||
self.trigger_call_popup()
|
||||
|
||||
def trigger_call_popup(self):
|
||||
scheduled_employees = get_scheduled_employees_for_popup(self.to)
|
||||
employee_emails = get_employees_with_number(self.to)
|
||||
|
||||
# check if employees with matched number are scheduled to receive popup
|
||||
emails = set(scheduled_employees).intersection(employee_emails)
|
||||
|
||||
# # if no employee found with matching phone number then show popup to scheduled employees
|
||||
# emails = emails or scheduled_employees if employee_emails
|
||||
|
||||
for email in emails:
|
||||
frappe.publish_realtime('show_call_popup', self, user=email)
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_call_summary(call_log, summary):
|
||||
doc = frappe.get_doc('Call Log', call_log)
|
||||
doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '<br><br>' + summary)
|
||||
|
||||
def get_employees_with_number(number):
|
||||
number = strip_number(number)
|
||||
if not number: return []
|
||||
|
||||
employee_emails = frappe.cache().hget('employees_with_number', number)
|
||||
if employee_emails: return employee_emails
|
||||
|
||||
employees = frappe.get_all('Employee', filters={
|
||||
'cell_number': ['like', '%{}%'.format(number)],
|
||||
'user_id': ['!=', '']
|
||||
}, fields=['user_id'])
|
||||
|
||||
employee_emails = [employee.user_id for employee in employees]
|
||||
frappe.cache().hset('employees_with_number', number, employee_emails)
|
||||
|
||||
return employee
|
||||
|
||||
def set_caller_information(doc, state):
|
||||
'''Called from hooks on creation of Lead or Contact'''
|
||||
if doc.doctype not in ['Lead', 'Contact']: return
|
||||
|
||||
numbers = [doc.get('phone'), doc.get('mobile_no')]
|
||||
# contact for Contact and lead for Lead
|
||||
fieldname = doc.doctype.lower()
|
||||
|
||||
# contact_name or lead_name
|
||||
display_name_field = '{}_name'.format(fieldname)
|
||||
|
||||
for number in numbers:
|
||||
number = strip_number(number)
|
||||
if not number: continue
|
||||
|
||||
filters = frappe._dict({
|
||||
'from': ['like', '%{}'.format(number)],
|
||||
fieldname: ''
|
||||
})
|
||||
|
||||
logs = frappe.get_all('Call Log', filters=filters)
|
||||
|
||||
for log in logs:
|
||||
frappe.db.set_value('Call Log', log.name, {
|
||||
fieldname: doc.name,
|
||||
display_name_field: doc.get_title()
|
||||
}, update_modified=False)
|
||||
|
@ -134,6 +134,12 @@ def get_data():
|
||||
"name": "Employee Leave Balance",
|
||||
"doctype": "Leave Application"
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "Leave Ledger Entry",
|
||||
"doctype": "Leave Ledger Entry"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -213,6 +219,16 @@ def get_data():
|
||||
"name": "Employee Benefit Claim",
|
||||
"dependencies": ["Employee"]
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Employee Tax Exemption Category",
|
||||
"dependencies": ["Employee"]
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Employee Tax Exemption Sub Category",
|
||||
"dependencies": ["Employee"]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -1192,7 +1192,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
.format(child_item.idx, child_item.item_code))
|
||||
else:
|
||||
child_item.rate = flt(d.get("rate"))
|
||||
if child_item.price_list_rate:
|
||||
|
||||
if flt(child_item.price_list_rate):
|
||||
child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, \
|
||||
child_item.precision("discount_percentage"))
|
||||
|
||||
|
@ -371,7 +371,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
return frappe.db.sql("""select tabAccount.name from `tabAccount`
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed"))
|
||||
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
|
||||
and tabAccount.is_group=0
|
||||
and tabAccount.docstatus!=2
|
||||
and tabAccount.{key} LIKE %(txt)s
|
||||
|
@ -18,34 +18,31 @@ def validate_return(doc):
|
||||
validate_returned_items(doc)
|
||||
|
||||
def validate_return_against(doc):
|
||||
filters = {"doctype": doc.doctype, "docstatus": 1, "company": doc.company}
|
||||
if doc.meta.get_field("customer") and doc.customer:
|
||||
filters["customer"] = doc.customer
|
||||
elif doc.meta.get_field("supplier") and doc.supplier:
|
||||
filters["supplier"] = doc.supplier
|
||||
|
||||
if not frappe.db.exists(filters):
|
||||
if not frappe.db.exists(doc.doctype, doc.return_against):
|
||||
frappe.throw(_("Invalid {0}: {1}")
|
||||
.format(doc.meta.get_label("return_against"), doc.return_against))
|
||||
else:
|
||||
ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
|
||||
|
||||
# validate posting date time
|
||||
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
|
||||
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
|
||||
party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier"
|
||||
|
||||
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
|
||||
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
|
||||
if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1:
|
||||
# validate posting date time
|
||||
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
|
||||
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
|
||||
|
||||
# validate same exchange rate
|
||||
if doc.conversion_rate != ref_doc.conversion_rate:
|
||||
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
|
||||
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
|
||||
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
|
||||
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
|
||||
|
||||
# validate update stock
|
||||
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
|
||||
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
|
||||
.format(doc.return_against))
|
||||
# validate same exchange rate
|
||||
if doc.conversion_rate != ref_doc.conversion_rate:
|
||||
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
|
||||
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
|
||||
|
||||
# validate update stock
|
||||
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
|
||||
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
|
||||
.format(doc.return_against))
|
||||
|
||||
def validate_returned_items(doc):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
@ -145,6 +145,16 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_opportunity(source_name, target_doc=None):
|
||||
def set_missing_values(source, target):
|
||||
address = frappe.get_all('Dynamic Link', {
|
||||
'link_doctype': source.doctype,
|
||||
'link_name': source.name,
|
||||
'parenttype': 'Address',
|
||||
}, ['parent'], limit=1)
|
||||
|
||||
if address:
|
||||
target.customer_address = address[0].parent
|
||||
|
||||
target_doc = get_mapped_doc("Lead", source_name,
|
||||
{"Lead": {
|
||||
"doctype": "Opportunity",
|
||||
@ -157,7 +167,7 @@ def make_opportunity(source_name, target_doc=None):
|
||||
"email_id": "contact_email",
|
||||
"mobile_no": "contact_mobile"
|
||||
}
|
||||
}}, target_doc)
|
||||
}}, target_doc, set_missing_values)
|
||||
|
||||
return target_doc
|
||||
|
||||
@ -230,3 +240,15 @@ def make_lead_from_communication(communication, ignore_communication_links=False
|
||||
|
||||
link_communication_to_document(doc, "Lead", lead_name, ignore_communication_links)
|
||||
return lead_name
|
||||
|
||||
def get_lead_with_phone_number(number):
|
||||
if not number: return
|
||||
|
||||
leads = frappe.get_all('Lead', or_filters={
|
||||
'phone': ['like', '%{}'.format(number)],
|
||||
'mobile_no': ['like', '%{}'.format(number)]
|
||||
}, limit=1)
|
||||
|
||||
lead = leads[0].name if leads else None
|
||||
|
||||
return lead
|
@ -31,9 +31,9 @@ frappe.ui.form.on("Opportunity", {
|
||||
|
||||
party_name: function(frm) {
|
||||
frm.toggle_display("contact_info", frm.doc.party_name);
|
||||
frm.trigger('set_contact_link');
|
||||
|
||||
if (frm.doc.opportunity_from == "Customer") {
|
||||
frm.trigger('set_contact_link');
|
||||
erpnext.utils.get_party_details(frm);
|
||||
} else if (frm.doc.opportunity_from == "Lead") {
|
||||
erpnext.utils.map_current_doc({
|
||||
@ -48,13 +48,6 @@ frappe.ui.form.on("Opportunity", {
|
||||
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
|
||||
},
|
||||
|
||||
party_name: function(frm) {
|
||||
if (frm.doc.opportunity_from == "Customer") {
|
||||
frm.trigger('set_contact_link');
|
||||
erpnext.utils.get_party_details(frm);
|
||||
}
|
||||
},
|
||||
|
||||
with_items: function(frm) {
|
||||
frm.trigger('toggle_mandatory');
|
||||
},
|
||||
|
@ -3,82 +3,59 @@ from frappe import _
|
||||
import json
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_document_with_phone_number(number):
|
||||
# finds contacts and leads
|
||||
if not number: return
|
||||
number = number.lstrip('0')
|
||||
number_filter = {
|
||||
'phone': ['like', '%{}'.format(number)],
|
||||
'mobile_no': ['like', '%{}'.format(number)]
|
||||
}
|
||||
contacts = frappe.get_all('Contact', or_filters=number_filter, limit=1)
|
||||
def get_last_interaction(contact=None, lead=None):
|
||||
|
||||
if contacts:
|
||||
return frappe.get_doc('Contact', contacts[0].name)
|
||||
if not contact and not lead: return
|
||||
|
||||
leads = frappe.get_all('Lead', or_filters=number_filter, limit=1)
|
||||
|
||||
if leads:
|
||||
return frappe.get_doc('Lead', leads[0].name)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_last_interaction(number, reference_doc):
|
||||
reference_doc = json.loads(reference_doc) if reference_doc else get_document_with_phone_number(number)
|
||||
|
||||
if not reference_doc: return
|
||||
|
||||
reference_doc = frappe._dict(reference_doc)
|
||||
|
||||
last_communication = {}
|
||||
last_issue = {}
|
||||
if reference_doc.doctype == 'Contact':
|
||||
customer_name = ''
|
||||
last_communication = None
|
||||
last_issue = None
|
||||
if contact:
|
||||
query_condition = ''
|
||||
for link in reference_doc.links:
|
||||
link = frappe._dict(link)
|
||||
values = []
|
||||
contact = frappe.get_doc('Contact', contact)
|
||||
for link in contact.links:
|
||||
if link.link_doctype == 'Customer':
|
||||
customer_name = link.link_name
|
||||
query_condition += "(`reference_doctype`='{}' AND `reference_name`='{}') OR".format(link.link_doctype, link.link_name)
|
||||
last_issue = get_last_issue_from_customer(link.link_name)
|
||||
query_condition += "(`reference_doctype`=%s AND `reference_name`=%s) OR"
|
||||
values += [link_link_doctype, link_link_name]
|
||||
|
||||
if query_condition:
|
||||
# remove extra appended 'OR'
|
||||
query_condition = query_condition[:-2]
|
||||
last_communication = frappe.db.sql("""
|
||||
SELECT `name`, `content`
|
||||
FROM `tabCommunication`
|
||||
WHERE {}
|
||||
WHERE `sent_or_received`='Received'
|
||||
AND ({})
|
||||
ORDER BY `modified`
|
||||
LIMIT 1
|
||||
""".format(query_condition)) # nosec
|
||||
""".format(query_condition), values, as_dict=1) # nosec
|
||||
|
||||
if customer_name:
|
||||
last_issue = frappe.get_all('Issue', {
|
||||
'customer': customer_name
|
||||
}, ['name', 'subject', 'customer'], limit=1)
|
||||
|
||||
elif reference_doc.doctype == 'Lead':
|
||||
if lead:
|
||||
last_communication = frappe.get_all('Communication', filters={
|
||||
'reference_doctype': reference_doc.doctype,
|
||||
'reference_name': reference_doc.name,
|
||||
'reference_doctype': 'Lead',
|
||||
'reference_name': lead,
|
||||
'sent_or_received': 'Received'
|
||||
}, fields=['name', 'content'], limit=1)
|
||||
}, fields=['name', 'content'], order_by='`creation` DESC', limit=1)
|
||||
|
||||
last_communication = last_communication[0] if last_communication else None
|
||||
|
||||
return {
|
||||
'last_communication': last_communication[0] if last_communication else None,
|
||||
'last_issue': last_issue[0] if last_issue else None
|
||||
'last_communication': last_communication,
|
||||
'last_issue': last_issue
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_call_summary(docname, summary):
|
||||
call_log = frappe.get_doc('Call Log', docname)
|
||||
summary = _('Call Summary by {0}: {1}').format(
|
||||
frappe.utils.get_fullname(frappe.session.user), summary)
|
||||
if not call_log.summary:
|
||||
call_log.summary = summary
|
||||
else:
|
||||
call_log.summary += '<br>' + summary
|
||||
call_log.save(ignore_permissions=True)
|
||||
def get_last_issue_from_customer(customer_name):
|
||||
issues = frappe.get_all('Issue', {
|
||||
'customer': customer_name
|
||||
}, ['name', 'subject', 'customer'], order_by='`creation` DESC', limit=1)
|
||||
|
||||
return issues[0] if issues else None
|
||||
|
||||
|
||||
def get_scheduled_employees_for_popup(communication_medium):
|
||||
if not communication_medium: return []
|
||||
|
||||
def get_employee_emails_for_popup(communication_medium):
|
||||
now_time = frappe.utils.nowtime()
|
||||
weekday = frappe.utils.get_weekday()
|
||||
|
||||
@ -98,3 +75,10 @@ def get_employee_emails_for_popup(communication_medium):
|
||||
employee_emails = set([employee.user_id for employee in employees])
|
||||
|
||||
return employee_emails
|
||||
|
||||
def strip_number(number):
|
||||
if not number: return
|
||||
# strip 0 from the start of the number for proper number comparisions
|
||||
# eg. 07888383332 should match with 7888383332
|
||||
number = number.lstrip('0')
|
||||
return number
|
@ -18,6 +18,8 @@ def handle_incoming_call(**kwargs):
|
||||
call_log = get_call_log(call_payload)
|
||||
if not call_log:
|
||||
create_call_log(call_payload)
|
||||
else:
|
||||
update_call_log(call_payload, call_log=call_log)
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def handle_end_call(**kwargs):
|
||||
@ -27,10 +29,11 @@ def handle_end_call(**kwargs):
|
||||
def handle_missed_call(**kwargs):
|
||||
update_call_log(kwargs, 'Missed')
|
||||
|
||||
def update_call_log(call_payload, status):
|
||||
call_log = get_call_log(call_payload)
|
||||
def update_call_log(call_payload, status='Ringing', call_log=None):
|
||||
call_log = call_log or get_call_log(call_payload)
|
||||
if call_log:
|
||||
call_log.status = status
|
||||
call_log.to = call_payload.get('DialWhomNumber')
|
||||
call_log.duration = call_payload.get('DialCallDuration') or 0
|
||||
call_log.recording_url = call_payload.get('RecordingUrl')
|
||||
call_log.save(ignore_permissions=True)
|
||||
@ -48,7 +51,7 @@ def get_call_log(call_payload):
|
||||
def create_call_log(call_payload):
|
||||
call_log = frappe.new_doc('Call Log')
|
||||
call_log.id = call_payload.get('CallSid')
|
||||
call_log.to = call_payload.get('CallTo')
|
||||
call_log.to = call_payload.get('DialWhomNumber')
|
||||
call_log.medium = call_payload.get('To')
|
||||
call_log.status = 'Ringing'
|
||||
setattr(call_log, 'from', call_payload.get('CallFrom'))
|
||||
|
@ -231,8 +231,12 @@ doc_events = {
|
||||
('Sales Invoice', 'Purchase Invoice', 'Delivery Note'): {
|
||||
'validate': 'erpnext.regional.india.utils.set_place_of_supply'
|
||||
},
|
||||
"Contact":{
|
||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue"
|
||||
"Contact": {
|
||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
|
||||
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
|
||||
},
|
||||
"Lead": {
|
||||
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
|
||||
},
|
||||
"Email Unsubscribe": {
|
||||
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
|
||||
@ -279,7 +283,9 @@ scheduler_events = {
|
||||
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
|
||||
],
|
||||
"daily_long": [
|
||||
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"
|
||||
"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.utils.generate_leave_encashment"
|
||||
],
|
||||
"monthly_long": [
|
||||
"erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"creation": "2013-01-10 16:34:13",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"attendance_details",
|
||||
"naming_series",
|
||||
@ -19,7 +20,9 @@
|
||||
"department",
|
||||
"shift",
|
||||
"attendance_request",
|
||||
"amended_from"
|
||||
"amended_from",
|
||||
"late_entry",
|
||||
"early_exit"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -153,12 +156,24 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Shift",
|
||||
"options": "Shift Type"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "late_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Late Entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "early_exit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Early Exit"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-ok",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-06-05 19:37:30.410071",
|
||||
"modified": "2019-07-29 20:35:40.845422",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Attendance",
|
||||
|
@ -76,6 +76,7 @@ class Employee(NestedSet):
|
||||
if self.user_id:
|
||||
self.update_user()
|
||||
self.update_user_permissions()
|
||||
self.reset_employee_emails_cache()
|
||||
|
||||
def update_user_permissions(self):
|
||||
if not self.create_user_permission: return
|
||||
@ -214,6 +215,15 @@ class Employee(NestedSet):
|
||||
doc.validate_employee_creation()
|
||||
doc.db_set("employee", self.name)
|
||||
|
||||
def reset_employee_emails_cache(self):
|
||||
prev_doc = self.get_doc_before_save() or {}
|
||||
cell_number = self.get('cell_number')
|
||||
prev_number = prev_doc.get('cell_number')
|
||||
if (cell_number != prev_number or
|
||||
self.get('user_id') != prev_doc.get('user_id')):
|
||||
frappe.cache().hdel('employees_with_number', cell_number)
|
||||
frappe.cache().hdel('employees_with_number', prev_number)
|
||||
|
||||
def get_timeline_data(doctype, name):
|
||||
'''Return timeline for attendance'''
|
||||
return dict(frappe.db.sql('''select unix_timestamp(attendance_date), count(*)
|
||||
|
@ -14,8 +14,6 @@
|
||||
"device_id",
|
||||
"skip_auto_attendance",
|
||||
"attendance",
|
||||
"entry_grace_period_consequence",
|
||||
"exit_grace_period_consequence",
|
||||
"shift_start",
|
||||
"shift_end",
|
||||
"shift_actual_start",
|
||||
@ -80,20 +78,6 @@
|
||||
"options": "Attendance",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "entry_grace_period_consequence",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Entry Grace Period Consequence"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "exit_grace_period_consequence",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Exit Grace Period Consequence"
|
||||
},
|
||||
{
|
||||
"fieldname": "shift_start",
|
||||
"fieldtype": "Datetime",
|
||||
@ -119,7 +103,7 @@
|
||||
"label": "Shift Actual End"
|
||||
}
|
||||
],
|
||||
"modified": "2019-06-10 15:33:22.731697",
|
||||
"modified": "2019-07-23 23:47:33.975263",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Checkin",
|
||||
|
@ -72,7 +72,7 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N
|
||||
return doc
|
||||
|
||||
|
||||
def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, shift=None):
|
||||
def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None):
|
||||
"""Creates an attendance and links the attendance to the Employee Checkin.
|
||||
Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown.
|
||||
|
||||
@ -98,7 +98,9 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
|
||||
'status': attendance_status,
|
||||
'working_hours': working_hours,
|
||||
'company': employee_doc.company,
|
||||
'shift': shift
|
||||
'shift': shift,
|
||||
'late_entry': late_entry,
|
||||
'early_exit': early_exit
|
||||
}
|
||||
attendance = frappe.get_doc(doc_dict).insert()
|
||||
attendance.submit()
|
||||
@ -124,11 +126,16 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
||||
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
|
||||
"""
|
||||
total_hours = 0
|
||||
in_time = out_time = None
|
||||
if check_in_out_type == 'Alternating entries as IN and OUT during the same shift':
|
||||
in_time = logs[0].time
|
||||
if len(logs) >= 2:
|
||||
out_time = logs[-1].time
|
||||
if working_hours_calc_type == 'First Check-in and Last Check-out':
|
||||
# assumption in this case: First log always taken as IN, Last log always taken as OUT
|
||||
total_hours = time_diff_in_hours(logs[0].time, logs[-1].time)
|
||||
total_hours = time_diff_in_hours(in_time, logs[-1].time)
|
||||
elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
|
||||
logs = logs[:]
|
||||
while len(logs) >= 2:
|
||||
total_hours += time_diff_in_hours(logs[0].time, logs[1].time)
|
||||
del logs[:2]
|
||||
@ -138,11 +145,15 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
||||
first_in_log = logs[find_index_in_dict(logs, 'log_type', 'IN')]
|
||||
last_out_log = logs[len(logs)-1-find_index_in_dict(reversed(logs), 'log_type', 'OUT')]
|
||||
if first_in_log and last_out_log:
|
||||
total_hours = time_diff_in_hours(first_in_log.time, last_out_log.time)
|
||||
in_time, out_time = first_in_log.time, last_out_log.time
|
||||
total_hours = time_diff_in_hours(in_time, out_time)
|
||||
elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
|
||||
in_log = out_log = None
|
||||
for log in logs:
|
||||
if in_log and out_log:
|
||||
if not in_time:
|
||||
in_time = in_log.time
|
||||
out_time = out_log.time
|
||||
total_hours += time_diff_in_hours(in_log.time, out_log.time)
|
||||
in_log = out_log = None
|
||||
if not in_log:
|
||||
@ -150,8 +161,9 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
||||
elif not out_log:
|
||||
out_log = log if log.log_type == 'OUT' else None
|
||||
if in_log and out_log:
|
||||
out_time = out_log.time
|
||||
total_hours += time_diff_in_hours(in_log.time, out_log.time)
|
||||
return total_hours
|
||||
return total_hours, in_time, out_time
|
||||
|
||||
def time_diff_in_hours(start, end):
|
||||
return round((end-start).total_seconds() / 3600, 1)
|
||||
|
@ -70,16 +70,16 @@ class TestEmployeeCheckin(unittest.TestCase):
|
||||
logs_type_2 = [frappe._dict(x) for x in logs_type_2]
|
||||
|
||||
working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[0])
|
||||
self.assertEqual(working_hours, 6.5)
|
||||
self.assertEqual(working_hours, (6.5, logs_type_1[0].time, logs_type_1[-1].time))
|
||||
|
||||
working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[1])
|
||||
self.assertEqual(working_hours, 4.5)
|
||||
self.assertEqual(working_hours, (4.5, logs_type_1[0].time, logs_type_1[-1].time))
|
||||
|
||||
working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[0])
|
||||
self.assertEqual(working_hours, 5)
|
||||
self.assertEqual(working_hours, (5, logs_type_2[1].time, logs_type_2[-1].time))
|
||||
|
||||
working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[1])
|
||||
self.assertEqual(working_hours, 4.5)
|
||||
self.assertEqual(working_hours, (4.5, logs_type_2[1].time, logs_type_2[-1].time))
|
||||
|
||||
def make_n_checkins(employee, n, hours_to_reverse=1):
|
||||
logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n+1))]
|
||||
|
@ -210,10 +210,42 @@
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "notify_users_by_email",
|
||||
"fieldtype": "Check",
|
||||
"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": "Notify users by email",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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,
|
||||
@ -548,7 +580,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:55.968224",
|
||||
"modified": "2019-08-01 16:15:55.968224",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Onboarding",
|
||||
|
@ -29,6 +29,9 @@ class EmployeeOnboarding(EmployeeBoardingController):
|
||||
def on_submit(self):
|
||||
super(EmployeeOnboarding, self).on_submit()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
self.create_task_and_notify_user()
|
||||
|
||||
def on_cancel(self):
|
||||
super(EmployeeOnboarding, self).on_cancel()
|
||||
|
||||
|
@ -145,40 +145,43 @@
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "project",
|
||||
"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": "Project",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Project",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "notify_users_by_email",
|
||||
"fieldtype": "Check",
|
||||
"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": "Notify users by email",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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,
|
||||
@ -276,7 +279,40 @@
|
||||
"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": "project",
|
||||
"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": "Project",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Project",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"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,
|
||||
@ -550,7 +586,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:39.025898",
|
||||
"modified": "2019-08-03 16:15:39.025898",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Separation",
|
||||
|
@ -11,6 +11,9 @@ class EmployeeSeparation(EmployeeBoardingController):
|
||||
|
||||
def on_submit(self):
|
||||
super(EmployeeSeparation, self).on_submit()
|
||||
|
||||
|
||||
def on_update_after_submit(self):
|
||||
self.create_task_and_notify_user()
|
||||
|
||||
def on_cancel(self):
|
||||
super(EmployeeSeparation, self).on_cancel()
|
||||
|
@ -24,6 +24,7 @@
|
||||
"column_break_18",
|
||||
"leave_approver_mandatory_in_leave_application",
|
||||
"show_leaves_of_all_department_members_in_calendar",
|
||||
"auto_leave_encashment",
|
||||
"hiring_settings",
|
||||
"check_vacancies"
|
||||
],
|
||||
@ -153,12 +154,18 @@
|
||||
"fieldname": "check_vacancies",
|
||||
"fieldtype": "Check",
|
||||
"label": "Check Vacancies On Job Offer Creation"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "auto_leave_encashment",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Leave Encashment"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"modified": "2019-07-01 18:59:55.256878",
|
||||
"modified": "2019-08-05 13:07:17.993968",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "HR Settings",
|
||||
|
@ -21,11 +21,41 @@ frappe.ui.form.on("Leave Allocation", {
|
||||
})
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.docstatus === 1 && frm.doc.expired) {
|
||||
var valid_expiry = moment(frappe.datetime.get_today()).isBetween(frm.doc.from_date, frm.doc.to_date);
|
||||
if(valid_expiry) {
|
||||
// expire current allocation
|
||||
frm.add_custom_button(__('Expire Allocation'), function() {
|
||||
frm.trigger("expire_allocation");
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
expire_allocation: function(frm) {
|
||||
frappe.call({
|
||||
method: 'erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.expire_allocation',
|
||||
args: {
|
||||
'allocation': frm.doc,
|
||||
'expiry_date': frappe.datetime.get_today()
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r){
|
||||
if(!r.exc){
|
||||
frappe.msgprint(__("Allocation Expired!"));
|
||||
}
|
||||
frm.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
frm.trigger("calculate_total_leaves_allocated");
|
||||
},
|
||||
|
||||
leave_type: function(frm) {
|
||||
frm.trigger("leave_policy");
|
||||
frm.trigger("calculate_total_leaves_allocated");
|
||||
},
|
||||
|
||||
@ -33,37 +63,38 @@ frappe.ui.form.on("Leave Allocation", {
|
||||
frm.trigger("calculate_total_leaves_allocated");
|
||||
},
|
||||
|
||||
carry_forwarded_leaves: function(frm) {
|
||||
unused_leaves: function(frm) {
|
||||
frm.set_value("total_leaves_allocated",
|
||||
flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated));
|
||||
flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated));
|
||||
},
|
||||
|
||||
new_leaves_allocated: function(frm) {
|
||||
frm.set_value("total_leaves_allocated",
|
||||
flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated));
|
||||
flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated));
|
||||
},
|
||||
|
||||
leave_policy: function(frm) {
|
||||
if(frm.doc.leave_policy && frm.doc.leave_type) {
|
||||
frappe.db.get_value("Leave Policy Detail",{
|
||||
'parent': frm.doc.leave_policy,
|
||||
'leave_type': frm.doc.leave_type
|
||||
}, 'annual_allocation', (r) => {
|
||||
if (r && !r.exc) frm.set_value("new_leaves_allocated", flt(r.annual_allocation));
|
||||
}, "Leave Policy");
|
||||
}
|
||||
},
|
||||
calculate_total_leaves_allocated: function(frm) {
|
||||
if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) {
|
||||
return frappe.call({
|
||||
method: "erpnext.hr.doctype.leave_allocation.leave_allocation.get_carry_forwarded_leaves",
|
||||
args: {
|
||||
"employee": frm.doc.employee,
|
||||
"date": frm.doc.from_date,
|
||||
"leave_type": frm.doc.leave_type,
|
||||
"carry_forward": frm.doc.carry_forward
|
||||
},
|
||||
method: "set_total_leaves_allocated",
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
if (!r.exc && r.message) {
|
||||
frm.set_value('carry_forwarded_leaves', r.message);
|
||||
frm.set_value("total_leaves_allocated",
|
||||
flt(r.message) + flt(frm.doc.new_leaves_allocated));
|
||||
}
|
||||
frm.refresh_fields();
|
||||
}
|
||||
})
|
||||
} else if (cint(frm.doc.carry_forward) == 0) {
|
||||
frm.set_value("carry_forwarded_leaves", 0);
|
||||
frm.set_value("unused_leaves", 0);
|
||||
frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated));
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
@ -1,683 +1,220 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "naming_series:",
|
||||
"beta": 0,
|
||||
"creation": "2013-02-20 19:10:38",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
"employee",
|
||||
"employee_name",
|
||||
"department",
|
||||
"column_break1",
|
||||
"leave_type",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"section_break_6",
|
||||
"new_leaves_allocated",
|
||||
"carry_forward",
|
||||
"unused_leaves",
|
||||
"total_leaves_allocated",
|
||||
"total_leaves_encashed",
|
||||
"column_break_10",
|
||||
"compensatory_request",
|
||||
"leave_period",
|
||||
"leave_policy",
|
||||
"carry_forwarded_leaves_count",
|
||||
"expired",
|
||||
"amended_from",
|
||||
"notes",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"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": "Series",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "HR-LAL-.YYYY.-",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Employee",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "employee",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Employee",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "employee.employee_name",
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Employee Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "employee.department",
|
||||
"fieldname": "department",
|
||||
"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": "Department",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Department",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"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,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "leave_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Leave Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "leave_type",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Leave Type",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "From Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "To Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"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": "Allocation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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
|
||||
"label": "Allocation"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "new_leaves_allocated",
|
||||
"fieldtype": "Float",
|
||||
"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": "New Leaves Allocated",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
"label": "New Leaves Allocated"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "",
|
||||
"default": "0",
|
||||
"fieldname": "carry_forward",
|
||||
"fieldtype": "Check",
|
||||
"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": "Add unused leaves from previous allocations",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
"label": "Add unused leaves from previous allocations"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "carry_forward",
|
||||
"fieldname": "carry_forwarded_leaves",
|
||||
"fieldname": "unused_leaves",
|
||||
"fieldtype": "Float",
|
||||
"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": "Unused leaves",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "total_leaves_allocated",
|
||||
"fieldtype": "Float",
|
||||
"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": "Total Leaves Allocated",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.total_leaves_encashed>0",
|
||||
"fieldname": "total_leaves_encashed",
|
||||
"fieldtype": "Float",
|
||||
"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": "Total Leaves Encashed",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_10",
|
||||
"fieldtype": "Column Break",
|
||||
"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,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "compensatory_request",
|
||||
"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": "Compensatory Leave Request",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Compensatory Leave Request",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "leave_period",
|
||||
"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": 1,
|
||||
"label": "Leave Period",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Leave Period",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.leave_policy",
|
||||
"fieldname": "leave_policy",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Leave Policy",
|
||||
"options": "Leave Policy",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "expired",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Expired",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "amended_from",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Leave Allocation",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "notes",
|
||||
"fieldtype": "Section Break",
|
||||
"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": "Notes",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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
|
||||
"label": "Notes"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"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": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "reason",
|
||||
"oldfieldtype": "Small Text",
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"depends_on": "carry_forwarded_leaves_count",
|
||||
"fieldname": "carry_forwarded_leaves_count",
|
||||
"fieldtype": "Float",
|
||||
"label": "Carry Forwarded Leaves",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-ok",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-01-30 11:28:09.360525",
|
||||
"modified": "2019-08-08 15:08:42.440909",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Allocation",
|
||||
@ -689,15 +226,10 @@
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
@ -709,28 +241,19 @@
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "employee,employee_name,leave_type,total_leaves_allocated",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"timeline_field": "employee",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"timeline_field": "employee"
|
||||
}
|
@ -3,11 +3,11 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import flt, date_diff, formatdate
|
||||
from frappe.utils import flt, date_diff, formatdate, add_days, today, getdate
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period
|
||||
from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period
|
||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
class BackDatedAllocationError(frappe.ValidationError): pass
|
||||
@ -40,14 +40,18 @@ class LeaveAllocation(Document):
|
||||
frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")\
|
||||
.format(self.leave_type, self.employee))
|
||||
|
||||
def on_update_after_submit(self):
|
||||
self.validate_new_leaves_allocated_value()
|
||||
self.set_total_leaves_allocated()
|
||||
def on_submit(self):
|
||||
self.create_leave_ledger_entry()
|
||||
|
||||
frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves))
|
||||
frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated))
|
||||
# expire all unused leaves in the ledger on creation of carry forward allocation
|
||||
allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee)
|
||||
if self.carry_forward and allocation:
|
||||
expire_allocation(allocation)
|
||||
|
||||
self.validate_against_leave_applications()
|
||||
def on_cancel(self):
|
||||
self.create_leave_ledger_entry(submit=False)
|
||||
if self.carry_forward:
|
||||
self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
|
||||
|
||||
def validate_period(self):
|
||||
if date_diff(self.to_date, self.from_date) <= 0:
|
||||
@ -87,13 +91,32 @@ class LeaveAllocation(Document):
|
||||
BackDatedAllocationError)
|
||||
|
||||
def set_total_leaves_allocated(self):
|
||||
self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee,
|
||||
self.unused_leaves = get_carry_forwarded_leaves(self.employee,
|
||||
self.leave_type, self.from_date, self.carry_forward)
|
||||
|
||||
self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated)
|
||||
self.total_leaves_allocated = flt(self.unused_leaves) + flt(self.new_leaves_allocated)
|
||||
|
||||
if self.carry_forward:
|
||||
self.maintain_carry_forwarded_leaves()
|
||||
self.set_carry_forwarded_leaves_in_previous_allocation()
|
||||
|
||||
if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"):
|
||||
frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}".format(self.leave_type)))
|
||||
frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}").format(self.leave_type))
|
||||
|
||||
def maintain_carry_forwarded_leaves(self):
|
||||
''' Reduce the carry forwarded leaves to be within the maximum allowed leaves '''
|
||||
|
||||
max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
|
||||
if self.new_leaves_allocated <= max_leaves_allowed <= self.total_leaves_allocated:
|
||||
self.unused_leaves = max_leaves_allowed - flt(self.new_leaves_allocated)
|
||||
self.total_leaves_allocated = flt(max_leaves_allowed)
|
||||
|
||||
def set_carry_forwarded_leaves_in_previous_allocation(self, on_cancel=False):
|
||||
''' Set carry forwarded leaves in previous allocation '''
|
||||
previous_allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee)
|
||||
if on_cancel:
|
||||
self.unused_leaves = 0.0
|
||||
frappe.db.set_value("Leave Allocation", previous_allocation.name, 'carry_forwarded_leaves_count', self.unused_leaves)
|
||||
|
||||
def validate_total_leaves_allocated(self):
|
||||
# Adding a day to include To Date in the difference
|
||||
@ -101,15 +124,37 @@ class LeaveAllocation(Document):
|
||||
if date_difference < self.total_leaves_allocated:
|
||||
frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError)
|
||||
|
||||
def validate_against_leave_applications(self):
|
||||
leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
|
||||
self.from_date, self.to_date)
|
||||
def create_leave_ledger_entry(self, submit=True):
|
||||
if self.unused_leaves:
|
||||
expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carry_forwarded_leaves_after_days")
|
||||
end_date = add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date
|
||||
args = dict(
|
||||
leaves=self.unused_leaves,
|
||||
from_date=self.from_date,
|
||||
to_date= min(getdate(end_date), getdate(self.to_date)),
|
||||
is_carry_forward=1
|
||||
)
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
if flt(leaves_taken) > flt(self.total_leaves_allocated):
|
||||
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
|
||||
frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken))
|
||||
else:
|
||||
frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError)
|
||||
args = dict(
|
||||
leaves=self.new_leaves_allocated,
|
||||
from_date=self.from_date,
|
||||
to_date=self.to_date,
|
||||
is_carry_forward=0
|
||||
)
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
def get_previous_allocation(from_date, leave_type, employee):
|
||||
''' Returns document properties of previous allocation '''
|
||||
return frappe.db.get_value("Leave Allocation",
|
||||
filters={
|
||||
'to_date': ("<", from_date),
|
||||
'leave_type': leave_type,
|
||||
'employee': employee,
|
||||
'docstatus': 1
|
||||
},
|
||||
order_by='to_date DESC',
|
||||
fieldname=['name', 'from_date', 'to_date', 'employee', 'leave_type'], as_dict=1)
|
||||
|
||||
def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
|
||||
leave_allocated = 0
|
||||
@ -136,25 +181,28 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None):
|
||||
carry_forwarded_leaves = 0
|
||||
|
||||
if carry_forward:
|
||||
''' Returns carry forwarded leaves for the given employee '''
|
||||
unused_leaves = 0.0
|
||||
previous_allocation = get_previous_allocation(date, leave_type, employee)
|
||||
if carry_forward and previous_allocation:
|
||||
validate_carry_forward(leave_type)
|
||||
unused_leaves = get_unused_leaves(employee, leave_type, previous_allocation.from_date, previous_allocation.to_date)
|
||||
|
||||
previous_allocation = frappe.db.sql("""
|
||||
select name, from_date, to_date, total_leaves_allocated
|
||||
from `tabLeave Allocation`
|
||||
where employee=%s and leave_type=%s and docstatus=1 and to_date < %s
|
||||
order by to_date desc limit 1
|
||||
""", (employee, leave_type, date), as_dict=1)
|
||||
if previous_allocation:
|
||||
leaves_taken = get_approved_leaves_for_period(employee, leave_type,
|
||||
previous_allocation[0].from_date, previous_allocation[0].to_date)
|
||||
return unused_leaves
|
||||
|
||||
carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken)
|
||||
|
||||
return carry_forwarded_leaves
|
||||
def get_unused_leaves(employee, leave_type, from_date, to_date):
|
||||
''' Returns unused leaves between the given period while skipping leave allocation expiry '''
|
||||
leaves = frappe.get_all("Leave Ledger Entry", filters={
|
||||
'employee': employee,
|
||||
'leave_type': leave_type,
|
||||
'from_date': ('>=', from_date),
|
||||
'to_date': ('<=', to_date)
|
||||
}, or_filters={
|
||||
'is_expired': 0,
|
||||
'is_carry_forward': 1
|
||||
}, fields=['sum(leaves) as leaves'])
|
||||
return flt(leaves[0]['leaves'])
|
||||
|
||||
def validate_carry_forward(leave_type):
|
||||
if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
|
||||
frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
|
||||
frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
|
@ -12,4 +12,9 @@ def get_data():
|
||||
'items': ['Leave Encashment']
|
||||
}
|
||||
],
|
||||
'reports': [
|
||||
{
|
||||
'items': ['Employee Leave Balance']
|
||||
}
|
||||
]
|
||||
}
|
11
erpnext/hr/doctype/leave_allocation/leave_allocation_list.js
Normal file
11
erpnext/hr/doctype/leave_allocation/leave_allocation_list.js
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
// render
|
||||
frappe.listview_settings['Leave Allocation'] = {
|
||||
get_indicator: function(doc) {
|
||||
if(doc.status==="Expired") {
|
||||
return [__("Expired"), "darkgrey", "expired, =, 1"];
|
||||
}
|
||||
},
|
||||
};
|
@ -34,7 +34,7 @@ QUnit.test("Test: Leave allocation [HR]", function (assert) {
|
||||
() => assert.equal(today_date, cur_frm.doc.from_date,
|
||||
"from date correctly set"),
|
||||
// check for total leaves
|
||||
() => assert.equal(cur_frm.doc.carry_forwarded_leaves + 2, cur_frm.doc.total_leaves_allocated,
|
||||
() => assert.equal(cur_frm.doc.unused_leaves + 2, cur_frm.doc.total_leaves_allocated,
|
||||
"total leave calculation is correctly set"),
|
||||
() => done()
|
||||
]);
|
||||
|
@ -1,12 +1,14 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils import nowdate, add_months, getdate, add_days
|
||||
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation
|
||||
|
||||
class TestLeaveAllocation(unittest.TestCase):
|
||||
def test_overlapping_allocation(self):
|
||||
frappe.db.sql("delete from `tabLeave Allocation`")
|
||||
|
||||
|
||||
employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
|
||||
leaves = [
|
||||
{
|
||||
@ -18,7 +20,7 @@ class TestLeaveAllocation(unittest.TestCase):
|
||||
"from_date": getdate("2015-10-01"),
|
||||
"to_date": getdate("2015-10-31"),
|
||||
"new_leaves_allocated": 5,
|
||||
"docstatus": 1
|
||||
"docstatus": 1
|
||||
},
|
||||
{
|
||||
"doctype": "Leave Allocation",
|
||||
@ -28,17 +30,17 @@ class TestLeaveAllocation(unittest.TestCase):
|
||||
"leave_type": "_Test Leave Type",
|
||||
"from_date": getdate("2015-09-01"),
|
||||
"to_date": getdate("2015-11-30"),
|
||||
"new_leaves_allocated": 5
|
||||
"new_leaves_allocated": 5
|
||||
}
|
||||
]
|
||||
|
||||
frappe.get_doc(leaves[0]).save()
|
||||
self.assertRaises(frappe.ValidationError, frappe.get_doc(leaves[1]).save)
|
||||
|
||||
def test_invalid_period(self):
|
||||
|
||||
def test_invalid_period(self):
|
||||
employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
|
||||
|
||||
d = frappe.get_doc({
|
||||
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Leave Allocation",
|
||||
"__islocal": 1,
|
||||
"employee": employee.name,
|
||||
@ -46,15 +48,15 @@ class TestLeaveAllocation(unittest.TestCase):
|
||||
"leave_type": "_Test Leave Type",
|
||||
"from_date": getdate("2015-09-30"),
|
||||
"to_date": getdate("2015-09-1"),
|
||||
"new_leaves_allocated": 5
|
||||
"new_leaves_allocated": 5
|
||||
})
|
||||
|
||||
|
||||
#invalid period
|
||||
self.assertRaises(frappe.ValidationError, d.save)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, doc.save)
|
||||
|
||||
def test_allocated_leave_days_over_period(self):
|
||||
employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
|
||||
d = frappe.get_doc({
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Leave Allocation",
|
||||
"__islocal": 1,
|
||||
"employee": employee.name,
|
||||
@ -62,10 +64,102 @@ class TestLeaveAllocation(unittest.TestCase):
|
||||
"leave_type": "_Test Leave Type",
|
||||
"from_date": getdate("2015-09-1"),
|
||||
"to_date": getdate("2015-09-30"),
|
||||
"new_leaves_allocated": 35
|
||||
"new_leaves_allocated": 35
|
||||
})
|
||||
|
||||
#allocated leave more than period
|
||||
self.assertRaises(frappe.ValidationError, d.save)
|
||||
|
||||
#allocated leave more than period
|
||||
self.assertRaises(frappe.ValidationError, doc.save)
|
||||
|
||||
def test_carry_forward_calculation(self):
|
||||
frappe.db.sql("delete from `tabLeave Allocation`")
|
||||
frappe.db.sql("delete from `tabLeave Ledger Entry`")
|
||||
leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1)
|
||||
leave_type.submit()
|
||||
|
||||
# initial leave allocation
|
||||
leave_allocation = create_leave_allocation(
|
||||
leave_type="_Test_CF_leave",
|
||||
from_date=add_months(nowdate(), -12),
|
||||
to_date=add_months(nowdate(), -1),
|
||||
carry_forward=0)
|
||||
leave_allocation.submit()
|
||||
|
||||
# leave allocation with carry forward from previous allocation
|
||||
leave_allocation_1 = create_leave_allocation(
|
||||
leave_type="_Test_CF_leave",
|
||||
carry_forward=1)
|
||||
leave_allocation_1.submit()
|
||||
|
||||
self.assertEquals(leave_allocation.total_leaves_allocated, leave_allocation_1.unused_leaves)
|
||||
|
||||
def test_carry_forward_leaves_expiry(self):
|
||||
frappe.db.sql("delete from `tabLeave Allocation`")
|
||||
frappe.db.sql("delete from `tabLeave Ledger Entry`")
|
||||
leave_type = create_leave_type(
|
||||
leave_type_name="_Test_CF_leave_expiry",
|
||||
is_carry_forward=1,
|
||||
expire_carry_forwarded_leaves_after_days=90)
|
||||
leave_type.submit()
|
||||
|
||||
# initial leave allocation
|
||||
leave_allocation = create_leave_allocation(
|
||||
leave_type="_Test_CF_leave_expiry",
|
||||
from_date=add_months(nowdate(), -24),
|
||||
to_date=add_months(nowdate(), -12),
|
||||
carry_forward=0)
|
||||
leave_allocation.submit()
|
||||
|
||||
leave_allocation = create_leave_allocation(
|
||||
leave_type="_Test_CF_leave_expiry",
|
||||
from_date=add_days(nowdate(), -90),
|
||||
to_date=add_days(nowdate(), 100),
|
||||
carry_forward=1)
|
||||
leave_allocation.submit()
|
||||
|
||||
# expires all the carry forwarded leaves after 90 days
|
||||
process_expired_allocation()
|
||||
|
||||
# leave allocation with carry forward of only new leaves allocated
|
||||
leave_allocation_1 = create_leave_allocation(
|
||||
leave_type="_Test_CF_leave_expiry",
|
||||
carry_forward=1,
|
||||
from_date=add_months(nowdate(), 6),
|
||||
to_date=add_months(nowdate(), 12))
|
||||
leave_allocation_1.submit()
|
||||
|
||||
self.assertEquals(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated)
|
||||
|
||||
def test_creation_of_leave_ledger_entry_on_submit(self):
|
||||
frappe.db.sql("delete from `tabLeave Allocation`")
|
||||
|
||||
leave_allocation = create_leave_allocation()
|
||||
leave_allocation.submit()
|
||||
|
||||
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name))
|
||||
|
||||
self.assertEquals(len(leave_ledger_entry), 1)
|
||||
self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee)
|
||||
self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type)
|
||||
self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated)
|
||||
|
||||
# check if leave ledger entry is deleted on cancellation
|
||||
leave_allocation.cancel()
|
||||
self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name}))
|
||||
|
||||
def create_leave_allocation(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
|
||||
leave_allocation = frappe.get_doc({
|
||||
"doctype": "Leave Allocation",
|
||||
"__islocal": 1,
|
||||
"employee": args.employee or employee.name,
|
||||
"employee_name": args.employee_name or employee.employee_name,
|
||||
"leave_type": args.leave_type or "_Test Leave Type",
|
||||
"from_date": args.from_date or nowdate(),
|
||||
"new_leaves_allocated": args.new_leaves_created or 15,
|
||||
"carry_forward": args.carry_forward or 0,
|
||||
"to_date": args.to_date or add_months(nowdate(), 12)
|
||||
})
|
||||
return leave_allocation
|
||||
|
||||
test_dependencies = ["Employee", "Leave Type"]
|
@ -49,7 +49,7 @@ frappe.ui.form.on("Leave Application", {
|
||||
async: false,
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
date: frm.doc.posting_date
|
||||
date: frm.doc.from_date || frm.doc.posting_date
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc && r.message['leave_allocation']) {
|
||||
@ -60,9 +60,8 @@ frappe.ui.form.on("Leave Application", {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$("div").remove(".form-dashboard-section");
|
||||
let section = frm.dashboard.add_section(
|
||||
frm.dashboard.add_section(
|
||||
frappe.render_template('leave_application_dashboard', {
|
||||
data: leave_details
|
||||
})
|
||||
@ -115,6 +114,7 @@ frappe.ui.form.on("Leave Application", {
|
||||
},
|
||||
|
||||
from_date: function(frm) {
|
||||
frm.trigger("make_dashboard");
|
||||
frm.trigger("half_day_datepicker");
|
||||
frm.trigger("calculate_total_days");
|
||||
},
|
||||
@ -138,12 +138,13 @@ frappe.ui.form.on("Leave Application", {
|
||||
},
|
||||
|
||||
get_leave_balance: function(frm) {
|
||||
if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date) {
|
||||
if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) {
|
||||
return frappe.call({
|
||||
method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
date: frm.doc.from_date,
|
||||
to_date: frm.doc.to_date,
|
||||
leave_type: frm.doc.leave_type,
|
||||
consider_all_leaves_in_the_allocation_period: true
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,11 +5,12 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
|
||||
comma_or, get_fullname, add_days, nowdate
|
||||
comma_or, get_fullname, add_days, nowdate, get_datetime_str
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period
|
||||
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
|
||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
|
||||
|
||||
class LeaveDayBlockedError(frappe.ValidationError): pass
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
@ -50,6 +51,7 @@ class LeaveApplication(Document):
|
||||
|
||||
# notify leave applier about approval
|
||||
self.notify_employee()
|
||||
self.create_leave_ledger_entry()
|
||||
self.reload()
|
||||
|
||||
def on_cancel(self):
|
||||
@ -57,6 +59,7 @@ class LeaveApplication(Document):
|
||||
# notify leave applier about cancellation
|
||||
self.notify_employee()
|
||||
self.cancel_attendance()
|
||||
self.create_leave_ledger_entry(submit=False)
|
||||
|
||||
def validate_applicable_after(self):
|
||||
if self.leave_type:
|
||||
@ -193,9 +196,9 @@ class LeaveApplication(Document):
|
||||
frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave."))
|
||||
|
||||
if not is_lwp(self.leave_type):
|
||||
self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, docname=self.name,
|
||||
self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, self.to_date,
|
||||
consider_all_leaves_in_the_allocation_period=True)
|
||||
if self.status != "Rejected" and self.leave_balance < self.total_leave_days:
|
||||
if self.status != "Rejected" and (self.leave_balance < self.total_leave_days or not self.leave_balance):
|
||||
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
|
||||
frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}")
|
||||
.format(self.leave_type))
|
||||
@ -347,6 +350,54 @@ class LeaveApplication(Document):
|
||||
except frappe.OutgoingEmailError:
|
||||
pass
|
||||
|
||||
def create_leave_ledger_entry(self, submit=True):
|
||||
expiry_date = get_allocation_expiry(self.employee, self.leave_type,
|
||||
self.to_date, self.from_date)
|
||||
|
||||
lwp = frappe.db.get_value("Leave Type", self.leave_type, "is_lwp")
|
||||
|
||||
if expiry_date:
|
||||
self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit, lwp)
|
||||
else:
|
||||
args = dict(
|
||||
leaves=self.total_leave_days * -1,
|
||||
from_date=self.from_date,
|
||||
to_date=self.to_date,
|
||||
is_lwp=lwp
|
||||
)
|
||||
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 '''
|
||||
args = dict(
|
||||
from_date=self.from_date,
|
||||
to_date=expiry_date,
|
||||
leaves=(date_diff(expiry_date, self.from_date) + 1) * -1,
|
||||
is_lwp=lwp
|
||||
)
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
if getdate(expiry_date) != getdate(self.to_date):
|
||||
start_date = add_days(expiry_date, 1)
|
||||
args.update(dict(
|
||||
from_date=start_date,
|
||||
to_date=self.to_date,
|
||||
leaves=date_diff(self.to_date, expiry_date) * -1
|
||||
))
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
def get_allocation_expiry(employee, leave_type, to_date, from_date):
|
||||
''' Returns expiry of carry forward allocation in leave ledger entry '''
|
||||
expiry = frappe.get_all("Leave Ledger Entry",
|
||||
filters={
|
||||
'employee': employee,
|
||||
'leave_type': leave_type,
|
||||
'is_carry_forward': 1,
|
||||
'transaction_type': 'Leave Allocation',
|
||||
'to_date': ['between', (from_date, to_date)]
|
||||
},fields=['to_date'])
|
||||
return expiry[0]['to_date'] if expiry else None
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None):
|
||||
number_of_days = 0
|
||||
@ -364,14 +415,16 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_leave_details(employee, date):
|
||||
allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict())
|
||||
allocation_records = get_leave_allocation_records(employee, date)
|
||||
leave_allocation = {}
|
||||
for d in allocation_records:
|
||||
allocation = allocation_records.get(d, frappe._dict())
|
||||
date = allocation.to_date
|
||||
leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date, status="Approved")
|
||||
leaves_pending = get_leaves_for_period(employee, d, allocation.from_date, date, status="Open")
|
||||
remaining_leaves = allocation.total_leaves_allocated - leaves_taken - leaves_pending
|
||||
remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date,
|
||||
consider_all_leaves_in_the_allocation_period=True)
|
||||
end_date = allocation.to_date
|
||||
leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, end_date) * -1
|
||||
leaves_pending = get_pending_leaves_for_period(employee, d, allocation.from_date, end_date)
|
||||
|
||||
leave_allocation[d] = {
|
||||
"total_leaves": allocation.total_leaves_allocated,
|
||||
"leaves_taken": leaves_taken,
|
||||
@ -386,27 +439,131 @@ def get_leave_details(employee, date):
|
||||
return ret
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_leave_balance_on(employee, leave_type, date, allocation_records=None, docname=None,
|
||||
consider_all_leaves_in_the_allocation_period=False, consider_encashed_leaves=True):
|
||||
def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), consider_all_leaves_in_the_allocation_period=False):
|
||||
'''
|
||||
Returns leave balance till date
|
||||
:param employee: employee name
|
||||
:param leave_type: leave type
|
||||
:param date: date to check balance on
|
||||
:param to_date: future date to check for allocation expiry
|
||||
:param consider_all_leaves_in_the_allocation_period: consider all leaves taken till the allocation end date
|
||||
'''
|
||||
|
||||
if allocation_records == None:
|
||||
allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict())
|
||||
allocation_records = get_leave_allocation_records(employee, date, leave_type)
|
||||
allocation = allocation_records.get(leave_type, frappe._dict())
|
||||
if consider_all_leaves_in_the_allocation_period:
|
||||
date = allocation.to_date
|
||||
leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, date, status="Approved", docname=docname)
|
||||
leaves_encashed = 0
|
||||
if frappe.db.get_value("Leave Type", leave_type, 'allow_encashment') and consider_encashed_leaves:
|
||||
leaves_encashed = flt(allocation.total_leaves_encashed)
|
||||
|
||||
return flt(allocation.total_leaves_allocated) - (flt(leaves_taken) + flt(leaves_encashed))
|
||||
end_date = allocation.to_date if consider_all_leaves_in_the_allocation_period else date
|
||||
expiry = get_allocation_expiry(employee, leave_type, to_date, date)
|
||||
|
||||
def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docname=None):
|
||||
leave_applications = frappe.db.sql("""
|
||||
select name, employee, leave_type, from_date, to_date, total_leave_days
|
||||
from `tabLeave Application`
|
||||
leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, end_date)
|
||||
|
||||
return get_remaining_leaves(allocation, leaves_taken, date, expiry)
|
||||
|
||||
def get_leave_allocation_records(employee, date, leave_type=None):
|
||||
''' returns the total allocated leaves and carry forwarded leaves based on ledger entries '''
|
||||
|
||||
conditions = ("and leave_type='%s'" % leave_type) if leave_type else ""
|
||||
allocation_details = frappe.db.sql("""
|
||||
SELECT
|
||||
SUM(CASE WHEN is_carry_forward = 1 THEN leaves ELSE 0 END) as cf_leaves,
|
||||
SUM(CASE WHEN is_carry_forward = 0 THEN leaves ELSE 0 END) as new_leaves,
|
||||
MIN(from_date) as from_date,
|
||||
MAX(to_date) as to_date,
|
||||
leave_type
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE
|
||||
from_date <= %(date)s
|
||||
AND to_date >= %(date)s
|
||||
AND docstatus=1
|
||||
AND transaction_type="Leave Allocation"
|
||||
AND employee=%(employee)s
|
||||
AND is_expired=0
|
||||
AND is_lwp=0
|
||||
{0}
|
||||
GROUP BY employee, leave_type
|
||||
""".format(conditions), dict(date=date, employee=employee), as_dict=1) #nosec
|
||||
|
||||
allocated_leaves = frappe._dict()
|
||||
for d in allocation_details:
|
||||
allocated_leaves.setdefault(d.leave_type, frappe._dict({
|
||||
"from_date": d.from_date,
|
||||
"to_date": d.to_date,
|
||||
"total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves),
|
||||
"unused_leaves": d.cf_leaves,
|
||||
"new_leaves_allocated": d.new_leaves,
|
||||
"leave_type": d.leave_type
|
||||
}))
|
||||
return allocated_leaves
|
||||
|
||||
def get_pending_leaves_for_period(employee, leave_type, from_date, to_date):
|
||||
''' Returns leaves that are pending approval '''
|
||||
return frappe.db.get_value("Leave Application",
|
||||
filters={
|
||||
"employee": employee,
|
||||
"leave_type": leave_type,
|
||||
"from_date": ("<=", from_date),
|
||||
"to_date": (">=", to_date),
|
||||
"status": "Open"
|
||||
}, fieldname=['SUM(total_leave_days)']) or flt(0)
|
||||
|
||||
def get_remaining_leaves(allocation, leaves_taken, date, expiry):
|
||||
''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry '''
|
||||
def _get_remaining_leaves(allocated_leaves, end_date):
|
||||
remaining_leaves = flt(allocated_leaves) + flt(leaves_taken)
|
||||
|
||||
if remaining_leaves > 0:
|
||||
remaining_days = date_diff(end_date, date) + 1
|
||||
remaining_leaves = min(remaining_days, remaining_leaves)
|
||||
|
||||
return remaining_leaves
|
||||
|
||||
total_leaves = allocation.total_leaves_allocated
|
||||
|
||||
if expiry and allocation.unused_leaves:
|
||||
remaining_leaves = _get_remaining_leaves(allocation.unused_leaves, expiry)
|
||||
|
||||
total_leaves = flt(allocation.new_leaves_allocated) + flt(remaining_leaves)
|
||||
|
||||
return _get_remaining_leaves(total_leaves, allocation.to_date)
|
||||
|
||||
def get_leaves_for_period(employee, leave_type, from_date, to_date):
|
||||
leave_entries = get_leave_entries(employee, leave_type, from_date, to_date)
|
||||
leave_days = 0
|
||||
|
||||
for leave_entry in leave_entries:
|
||||
inclusive_period = leave_entry.from_date >= getdate(from_date) and leave_entry.to_date <= getdate(to_date)
|
||||
|
||||
if inclusive_period and leave_entry.transaction_type == 'Leave Encashment':
|
||||
leave_days += leave_entry.leaves
|
||||
|
||||
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \
|
||||
and not skip_expiry_leaves(leave_entry, to_date):
|
||||
leave_days += leave_entry.leaves
|
||||
|
||||
else:
|
||||
if leave_entry.from_date < getdate(from_date):
|
||||
leave_entry.from_date = from_date
|
||||
if leave_entry.to_date > getdate(to_date):
|
||||
leave_entry.to_date = to_date
|
||||
|
||||
leave_days += get_number_of_leave_days(employee, leave_type,
|
||||
leave_entry.from_date, leave_entry.to_date) * -1
|
||||
|
||||
return leave_days
|
||||
|
||||
def skip_expiry_leaves(leave_entry, date):
|
||||
''' Checks whether the expired leaves coincide with the to_date of leave balance check '''
|
||||
end_date = frappe.db.get_value("Leave Allocation", {'name': leave_entry.transaction_name}, ['to_date'])
|
||||
return True if end_date == date and not leave_entry.is_carry_forward else False
|
||||
|
||||
def get_leave_entries(employee, leave_type, from_date, to_date):
|
||||
''' Returns leave entries between from_date and to_date '''
|
||||
return frappe.db.sql("""
|
||||
select employee, leave_type, from_date, to_date, leaves, transaction_type, is_carry_forward
|
||||
from `tabLeave Ledger Entry`
|
||||
where employee=%(employee)s and leave_type=%(leave_type)s
|
||||
and status = %(status)s and docstatus != 2
|
||||
and docstatus=1
|
||||
and leaves<0
|
||||
and (from_date between %(from_date)s and %(to_date)s
|
||||
or to_date between %(from_date)s and %(to_date)s
|
||||
or (from_date < %(from_date)s and to_date > %(to_date)s))
|
||||
@ -414,43 +571,8 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docn
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"employee": employee,
|
||||
"status": status,
|
||||
"leave_type": leave_type
|
||||
}, as_dict=1)
|
||||
leave_days = 0
|
||||
for leave_app in leave_applications:
|
||||
if docname and leave_app.name == docname:
|
||||
continue
|
||||
if leave_app.from_date >= getdate(from_date) and leave_app.to_date <= getdate(to_date):
|
||||
leave_days += leave_app.total_leave_days
|
||||
else:
|
||||
if leave_app.from_date < getdate(from_date):
|
||||
leave_app.from_date = from_date
|
||||
if leave_app.to_date > getdate(to_date):
|
||||
leave_app.to_date = to_date
|
||||
|
||||
leave_days += get_number_of_leave_days(employee, leave_type,
|
||||
leave_app.from_date, leave_app.to_date)
|
||||
|
||||
return leave_days
|
||||
|
||||
def get_leave_allocation_records(date, employee=None):
|
||||
conditions = (" and employee='%s'" % employee) if employee else ""
|
||||
|
||||
leave_allocation_records = frappe.db.sql("""
|
||||
select employee, leave_type, total_leaves_allocated, total_leaves_encashed, from_date, to_date
|
||||
from `tabLeave Allocation`
|
||||
where %s between from_date and to_date and docstatus=1 {0}""".format(conditions), (date), as_dict=1)
|
||||
|
||||
allocated_leaves = frappe._dict()
|
||||
for d in leave_allocation_records:
|
||||
allocated_leaves.setdefault(d.employee, frappe._dict()).setdefault(d.leave_type, frappe._dict({
|
||||
"from_date": d.from_date,
|
||||
"to_date": d.to_date,
|
||||
"total_leaves_allocated": d.total_leaves_allocated,
|
||||
"total_leaves_encashed":d.total_leaves_encashed
|
||||
}))
|
||||
return allocated_leaves
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_holidays(employee, from_date, to_date):
|
||||
@ -629,4 +751,4 @@ def get_leave_approver(employee, department=None):
|
||||
|
||||
if department:
|
||||
return frappe.db.get_value('Department Approver', {'parent': department,
|
||||
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
|
||||
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
|
@ -0,0 +1,14 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from frappe import _
|
||||
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'reports': [
|
||||
{
|
||||
'label': _('Reports'),
|
||||
'items': ['Employee Leave Balance']
|
||||
}
|
||||
]
|
||||
}
|
@ -7,7 +7,9 @@ import unittest
|
||||
|
||||
from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError, NotAnOptionalHoliday, get_leave_balance_on
|
||||
from frappe.permissions import clear_user_permissions_for_doctype
|
||||
from frappe.utils import add_days, nowdate, now_datetime, getdate
|
||||
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
|
||||
|
||||
test_dependencies = ["Leave Allocation", "Leave Block List"]
|
||||
|
||||
@ -17,6 +19,7 @@ _test_records = [
|
||||
"doctype": "Leave Application",
|
||||
"employee": "_T-Employee-00001",
|
||||
"from_date": "2013-05-01",
|
||||
"description": "_Test Reason",
|
||||
"leave_type": "_Test Leave Type",
|
||||
"posting_date": "2013-01-02",
|
||||
"to_date": "2013-05-05"
|
||||
@ -26,6 +29,7 @@ _test_records = [
|
||||
"doctype": "Leave Application",
|
||||
"employee": "_T-Employee-00002",
|
||||
"from_date": "2013-05-01",
|
||||
"description": "_Test Reason",
|
||||
"leave_type": "_Test Leave Type",
|
||||
"posting_date": "2013-01-02",
|
||||
"to_date": "2013-05-05"
|
||||
@ -35,6 +39,7 @@ _test_records = [
|
||||
"doctype": "Leave Application",
|
||||
"employee": "_T-Employee-00001",
|
||||
"from_date": "2013-01-15",
|
||||
"description": "_Test Reason",
|
||||
"leave_type": "_Test Leave Type LWP",
|
||||
"posting_date": "2013-01-02",
|
||||
"to_date": "2013-01-15"
|
||||
@ -44,8 +49,8 @@ _test_records = [
|
||||
|
||||
class TestLeaveApplication(unittest.TestCase):
|
||||
def setUp(self):
|
||||
for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]:
|
||||
frappe.db.sql("delete from `tab%s`" % dt)
|
||||
for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]:
|
||||
frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@ -268,13 +273,14 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
doctype = 'Leave Application',
|
||||
employee = employee.name,
|
||||
company = '_Test Company',
|
||||
description = "_Test Reason",
|
||||
leave_type = leave_type,
|
||||
from_date = date,
|
||||
to_date = date,
|
||||
))
|
||||
|
||||
# can only apply on optional holidays
|
||||
self.assertTrue(NotAnOptionalHoliday, leave_application.insert)
|
||||
self.assertRaises(NotAnOptionalHoliday, leave_application.insert)
|
||||
|
||||
leave_application.from_date = today
|
||||
leave_application.to_date = today
|
||||
@ -285,7 +291,6 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
# check leave balance is reduced
|
||||
self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9)
|
||||
|
||||
|
||||
def test_leaves_allowed(self):
|
||||
employee = get_employee()
|
||||
leave_period = get_leave_period()
|
||||
@ -301,24 +306,25 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
allocate_leaves(employee, leave_period, leave_type.name, 5)
|
||||
|
||||
leave_application = frappe.get_doc(dict(
|
||||
doctype = 'Leave Application',
|
||||
doctype = 'Leave Application',
|
||||
employee = employee.name,
|
||||
leave_type = leave_type.name,
|
||||
description = "_Test Reason",
|
||||
from_date = date,
|
||||
to_date = add_days(date, 2),
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
))
|
||||
|
||||
self.assertTrue(leave_application.insert())
|
||||
leave_application.submit()
|
||||
|
||||
leave_application = frappe.get_doc(dict(
|
||||
doctype = 'Leave Application',
|
||||
employee = employee.name,
|
||||
leave_type = leave_type.name,
|
||||
description = "_Test Reason",
|
||||
from_date = add_days(date, 4),
|
||||
to_date = add_days(date, 7),
|
||||
to_date = add_days(date, 8),
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
@ -342,6 +348,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
doctype = 'Leave Application',
|
||||
employee = employee.name,
|
||||
leave_type = leave_type.name,
|
||||
description = "_Test Reason",
|
||||
from_date = date,
|
||||
to_date = add_days(date, 4),
|
||||
company = "_Test Company",
|
||||
@ -363,6 +370,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
doctype = 'Leave Application',
|
||||
employee = employee.name,
|
||||
leave_type = leave_type_1.name,
|
||||
description = "_Test Reason",
|
||||
from_date = date,
|
||||
to_date = add_days(date, 4),
|
||||
company = "_Test Company",
|
||||
@ -392,6 +400,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
doctype = 'Leave Application',
|
||||
employee = employee.name,
|
||||
leave_type = leave_type.name,
|
||||
description = "_Test Reason",
|
||||
from_date = date,
|
||||
to_date = add_days(date, 4),
|
||||
company = "_Test Company",
|
||||
@ -401,6 +410,18 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, leave_application.insert)
|
||||
|
||||
def test_leave_balance_near_allocaton_expiry(self):
|
||||
employee = get_employee()
|
||||
leave_type = create_leave_type(
|
||||
leave_type_name="_Test_CF_leave_expiry",
|
||||
is_carry_forward=1,
|
||||
expire_carry_forwarded_leaves_after_days=90)
|
||||
leave_type.submit()
|
||||
|
||||
create_carry_forwarded_allocation(employee, leave_type)
|
||||
|
||||
self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8)), 21)
|
||||
|
||||
def test_earned_leave(self):
|
||||
leave_period = get_leave_period()
|
||||
employee = get_employee()
|
||||
@ -444,9 +465,10 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
allocation.insert(ignore_permissions=True)
|
||||
allocation.submit()
|
||||
leave_application = frappe.get_doc(dict(
|
||||
doctype = 'Leave Application',
|
||||
doctype = 'Leave Application',
|
||||
employee = employee.name,
|
||||
leave_type = leave_type,
|
||||
description = "_Test Reason",
|
||||
from_date = '2018-10-02',
|
||||
to_date = '2018-10-02',
|
||||
company = '_Test Company',
|
||||
@ -457,9 +479,103 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
leave_application.submit()
|
||||
self.assertEqual(leave_application.docstatus, 1)
|
||||
|
||||
def make_allocation_record(employee=None, leave_type=None):
|
||||
frappe.db.sql("delete from `tabLeave Allocation`")
|
||||
def test_creation_of_leave_ledger_entry_on_submit(self):
|
||||
employee = get_employee()
|
||||
|
||||
leave_type = create_leave_type(leave_type_name = 'Test Leave Type 1')
|
||||
leave_type.save()
|
||||
|
||||
leave_allocation = create_leave_allocation(employee=employee.name, employee_name=employee.employee_name,
|
||||
leave_type=leave_type.name)
|
||||
leave_allocation.submit()
|
||||
|
||||
leave_application = frappe.get_doc(dict(
|
||||
doctype = 'Leave Application',
|
||||
employee = employee.name,
|
||||
leave_type = leave_type.name,
|
||||
from_date = add_days(nowdate(), 1),
|
||||
to_date = add_days(nowdate(), 4),
|
||||
description = "_Test Reason",
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
))
|
||||
leave_application.submit()
|
||||
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
|
||||
|
||||
self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee)
|
||||
self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type)
|
||||
self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1)
|
||||
|
||||
# check if leave ledger entry is deleted on cancellation
|
||||
leave_application.cancel()
|
||||
self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_application.name}))
|
||||
|
||||
def test_ledger_entry_creation_on_intermediate_allocation_expiry(self):
|
||||
employee = get_employee()
|
||||
leave_type = create_leave_type(
|
||||
leave_type_name="_Test_CF_leave_expiry",
|
||||
is_carry_forward=1,
|
||||
expire_carry_forwarded_leaves_after_days=90)
|
||||
leave_type.submit()
|
||||
|
||||
create_carry_forwarded_allocation(employee, leave_type)
|
||||
|
||||
leave_application = frappe.get_doc(dict(
|
||||
doctype = 'Leave Application',
|
||||
employee = employee.name,
|
||||
leave_type = leave_type.name,
|
||||
from_date = add_days(nowdate(), -3),
|
||||
to_date = add_days(nowdate(), 7),
|
||||
description = "_Test Reason",
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
))
|
||||
leave_application.submit()
|
||||
|
||||
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name))
|
||||
|
||||
self.assertEquals(len(leave_ledger_entry), 2)
|
||||
self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee)
|
||||
self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type)
|
||||
self.assertEquals(leave_ledger_entry[0].leaves, -9)
|
||||
self.assertEquals(leave_ledger_entry[1].leaves, -2)
|
||||
|
||||
def test_leave_application_creation_after_expiry(self):
|
||||
# test leave balance for carry forwarded allocation
|
||||
employee = get_employee()
|
||||
leave_type = create_leave_type(
|
||||
leave_type_name="_Test_CF_leave_expiry",
|
||||
is_carry_forward=1,
|
||||
expire_carry_forwarded_leaves_after_days=90)
|
||||
leave_type.submit()
|
||||
|
||||
create_carry_forwarded_allocation(employee, leave_type)
|
||||
|
||||
self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
|
||||
|
||||
def create_carry_forwarded_allocation(employee, leave_type):
|
||||
# initial leave allocation
|
||||
leave_allocation = create_leave_allocation(
|
||||
leave_type="_Test_CF_leave_expiry",
|
||||
employee=employee.name,
|
||||
employee_name=employee.employee_name,
|
||||
from_date=add_months(nowdate(), -24),
|
||||
to_date=add_months(nowdate(), -12),
|
||||
carry_forward=0)
|
||||
leave_allocation.submit()
|
||||
|
||||
leave_allocation = create_leave_allocation(
|
||||
leave_type="_Test_CF_leave_expiry",
|
||||
employee=employee.name,
|
||||
employee_name=employee.employee_name,
|
||||
from_date=add_days(nowdate(), -84),
|
||||
to_date=add_days(nowdate(), 100),
|
||||
carry_forward=1)
|
||||
leave_allocation.submit()
|
||||
|
||||
def make_allocation_record(employee=None, leave_type=None):
|
||||
allocation = frappe.get_doc({
|
||||
"doctype": "Leave Allocation",
|
||||
"employee": employee or "_T-Employee-00001",
|
||||
@ -513,4 +629,4 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el
|
||||
"docstatus": 1
|
||||
}).insert()
|
||||
|
||||
allocate_leave.submit()
|
||||
allocate_leave.submit()
|
@ -10,6 +10,8 @@ from frappe.utils import getdate, nowdate, flt
|
||||
from erpnext.hr.utils import set_employee_name
|
||||
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
|
||||
from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
|
||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
|
||||
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
|
||||
|
||||
class LeaveEncashment(Document):
|
||||
def validate(self):
|
||||
@ -25,7 +27,7 @@ class LeaveEncashment(Document):
|
||||
|
||||
def on_submit(self):
|
||||
if not self.leave_allocation:
|
||||
self.leave_allocation = self.get_leave_allocation()
|
||||
self.leave_allocation = self.get_leave_allocation().get('name')
|
||||
additional_salary = frappe.new_doc("Additional Salary")
|
||||
additional_salary.company = frappe.get_value("Employee", self.employee, "company")
|
||||
additional_salary.employee = self.employee
|
||||
@ -40,6 +42,8 @@ class LeaveEncashment(Document):
|
||||
frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
|
||||
frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') + self.encashable_days)
|
||||
|
||||
self.create_leave_ledger_entry()
|
||||
|
||||
def on_cancel(self):
|
||||
if self.additional_salary:
|
||||
frappe.get_doc("Additional Salary", self.additional_salary).cancel()
|
||||
@ -48,6 +52,7 @@ class LeaveEncashment(Document):
|
||||
if self.leave_allocation:
|
||||
frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
|
||||
frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days)
|
||||
self.create_leave_ledger_entry(submit=False)
|
||||
|
||||
def get_leave_details_for_encashment(self):
|
||||
salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate()))
|
||||
@ -57,8 +62,10 @@ class LeaveEncashment(Document):
|
||||
if not frappe.db.get_value("Leave Type", self.leave_type, 'allow_encashment'):
|
||||
frappe.throw(_("Leave Type {0} is not encashable").format(self.leave_type))
|
||||
|
||||
self.leave_balance = get_leave_balance_on(self.employee, self.leave_type,
|
||||
self.encashment_date or getdate(nowdate()), consider_all_leaves_in_the_allocation_period=True)
|
||||
allocation = self.get_leave_allocation()
|
||||
|
||||
self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\
|
||||
- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date)
|
||||
|
||||
encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days')
|
||||
self.encashable_days = encashable_days if encashable_days > 0 else 0
|
||||
@ -66,12 +73,47 @@ class LeaveEncashment(Document):
|
||||
per_day_encashment = frappe.db.get_value('Salary Structure', salary_structure , 'leave_encashment_amount_per_day')
|
||||
self.encashment_amount = self.encashable_days * per_day_encashment if per_day_encashment > 0 else 0
|
||||
|
||||
self.leave_allocation = self.get_leave_allocation()
|
||||
self.leave_allocation = allocation.name
|
||||
return True
|
||||
|
||||
def get_leave_allocation(self):
|
||||
leave_allocation = frappe.db.sql("""select name from `tabLeave Allocation` where '{0}'
|
||||
leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}'
|
||||
between from_date and to_date and docstatus=1 and leave_type='{1}'
|
||||
and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee))
|
||||
and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) #nosec
|
||||
|
||||
return leave_allocation[0][0] if leave_allocation else None
|
||||
return leave_allocation[0] if leave_allocation else None
|
||||
|
||||
def create_leave_ledger_entry(self, submit=True):
|
||||
args = frappe._dict(
|
||||
leaves=self.encashable_days * -1,
|
||||
from_date=self.encashment_date,
|
||||
to_date=self.encashment_date,
|
||||
is_carry_forward=0
|
||||
)
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
# create reverse entry for expired leaves
|
||||
to_date = self.get_leave_allocation().get('to_date')
|
||||
if to_date < getdate(nowdate()):
|
||||
args = frappe._dict(
|
||||
leaves=self.encashable_days,
|
||||
from_date=to_date,
|
||||
to_date=to_date,
|
||||
is_carry_forward=0
|
||||
)
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
|
||||
def create_leave_encashment(leave_allocation):
|
||||
''' Creates leave encashment for the given allocations '''
|
||||
for allocation in leave_allocation:
|
||||
if not get_assigned_salary_structure(allocation.employee, allocation.to_date):
|
||||
continue
|
||||
leave_encashment = frappe.get_doc(dict(
|
||||
doctype="Leave Encashment",
|
||||
leave_period=allocation.leave_period,
|
||||
employee=allocation.employee,
|
||||
leave_type=allocation.leave_type,
|
||||
encashment_date=allocation.to_date
|
||||
))
|
||||
leave_encashment.insert(ignore_permissions=True)
|
@ -9,42 +9,43 @@ from frappe.utils import today, add_months
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.hr.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.test_leave_policy import create_leave_policy\
|
||||
|
||||
test_dependencies = ["Leave Type"]
|
||||
|
||||
class TestLeaveEncashment(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql('''delete from `tabLeave Period`''')
|
||||
def test_leave_balance_value_and_amount(self):
|
||||
employee = "test_employee_encashment@salary.com"
|
||||
leave_type = "_Test Leave Type Encashment"
|
||||
frappe.db.sql('''delete from `tabLeave Allocation`''')
|
||||
frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
|
||||
frappe.db.sql('''delete from `tabAdditional Salary`''')
|
||||
|
||||
# create the leave policy
|
||||
leave_policy = frappe.get_doc({
|
||||
"doctype": "Leave Policy",
|
||||
"leave_policy_details": [{
|
||||
"leave_type": leave_type,
|
||||
"annual_allocation": 10
|
||||
}]
|
||||
}).insert()
|
||||
leave_policy = create_leave_policy(
|
||||
leave_type="_Test Leave Type Encashment",
|
||||
annual_allocation=10)
|
||||
leave_policy.submit()
|
||||
|
||||
# create employee, salary structure and assignment
|
||||
employee = make_employee(employee)
|
||||
frappe.db.set_value("Employee", employee, "leave_policy", leave_policy.name)
|
||||
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", employee,
|
||||
self.employee = make_employee("test_employee_encashment@example.com")
|
||||
|
||||
frappe.db.set_value("Employee", self.employee, "leave_policy", leave_policy.name)
|
||||
|
||||
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
|
||||
leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
|
||||
leave_period.grant_leave_allocation(employee=employee)
|
||||
self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
|
||||
self.leave_period.grant_leave_allocation(employee=self.employee)
|
||||
|
||||
def test_leave_balance_value_and_amount(self):
|
||||
frappe.db.sql('''delete from `tabLeave Encashment`''')
|
||||
leave_encashment = frappe.get_doc(dict(
|
||||
doctype = 'Leave Encashment',
|
||||
employee = employee,
|
||||
leave_type = leave_type,
|
||||
leave_period = leave_period.name,
|
||||
payroll_date = today()
|
||||
doctype='Leave Encashment',
|
||||
employee=self.employee,
|
||||
leave_type="_Test Leave Type Encashment",
|
||||
leave_period=self.leave_period.name,
|
||||
payroll_date=today()
|
||||
)).insert()
|
||||
|
||||
self.assertEqual(leave_encashment.leave_balance, 10)
|
||||
@ -53,3 +54,26 @@ class TestLeaveEncashment(unittest.TestCase):
|
||||
|
||||
leave_encashment.submit()
|
||||
self.assertTrue(frappe.db.get_value("Leave Encashment", leave_encashment.name, "additional_salary"))
|
||||
|
||||
def test_creation_of_leave_ledger_entry_on_submit(self):
|
||||
frappe.db.sql('''delete from `tabLeave Encashment`''')
|
||||
leave_encashment = frappe.get_doc(dict(
|
||||
doctype='Leave Encashment',
|
||||
employee=self.employee,
|
||||
leave_type="_Test Leave Type Encashment",
|
||||
leave_period=self.leave_period.name,
|
||||
payroll_date=today()
|
||||
)).insert()
|
||||
|
||||
leave_encashment.submit()
|
||||
|
||||
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name))
|
||||
|
||||
self.assertEquals(len(leave_ledger_entry), 1)
|
||||
self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee)
|
||||
self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type)
|
||||
self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1)
|
||||
|
||||
# check if leave ledger entry is deleted on cancellation
|
||||
leave_encashment.cancel()
|
||||
self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_encashment.name}))
|
||||
|
0
erpnext/hr/doctype/leave_ledger_entry/__init__.py
Normal file
0
erpnext/hr/doctype/leave_ledger_entry/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Leave Ledger Entry', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
169
erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
Normal file
169
erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
Normal file
@ -0,0 +1,169 @@
|
||||
{
|
||||
"creation": "2019-05-09 15:47:39.760406",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"employee",
|
||||
"employee_name",
|
||||
"leave_type",
|
||||
"transaction_type",
|
||||
"transaction_name",
|
||||
"leaves",
|
||||
"column_break_7",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"is_carry_forward",
|
||||
"is_expired",
|
||||
"is_lwp",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Employee",
|
||||
"options": "Employee"
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.employee_name",
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Employee Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "leave_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Leave Type",
|
||||
"options": "Leave Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Leave Ledger Entry",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Transaction Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Transaction Name",
|
||||
"options": "transaction_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "leaves",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Leaves"
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_carry_forward",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Carry Forward"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_expired",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Expired"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_lwp",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Leave Without Pay"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-08-20 14:40:04.130799",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Ledger Entry",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"cancel": 1,
|
||||
"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,
|
||||
"if_owner": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"title_field": "employee"
|
||||
}
|
174
erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
Normal file
174
erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
Normal file
@ -0,0 +1,174 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, 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 _
|
||||
from frappe.utils import add_days, today, flt, DATE_FORMAT, getdate
|
||||
|
||||
class LeaveLedgerEntry(Document):
|
||||
def validate(self):
|
||||
if getdate(self.from_date) > getdate(self.to_date):
|
||||
frappe.throw(_("To date needs to be before from date"))
|
||||
|
||||
def on_cancel(self):
|
||||
# allow cancellation of expiry leaves
|
||||
if self.is_expired:
|
||||
frappe.db.set_value("Leave Allocation", self.transaction_name, "expired", 0)
|
||||
else:
|
||||
frappe.throw(_("Only expired allocation can be cancelled"))
|
||||
|
||||
def validate_leave_allocation_against_leave_application(ledger):
|
||||
''' Checks that leave allocation has no leave application against it '''
|
||||
leave_application_records = frappe.db.sql_list("""
|
||||
SELECT transaction_name
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE
|
||||
employee=%s
|
||||
AND leave_type=%s
|
||||
AND transaction_type='Leave Application'
|
||||
AND from_date>=%s
|
||||
AND to_date<=%s
|
||||
""", (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))
|
||||
|
||||
if leave_application_records:
|
||||
frappe.throw(_("Leave allocation %s is linked with leave application %s"
|
||||
% (ledger.transaction_name, ', '.join(leave_application_records))))
|
||||
|
||||
def create_leave_ledger_entry(ref_doc, args, submit=True):
|
||||
ledger = frappe._dict(
|
||||
doctype='Leave Ledger Entry',
|
||||
employee=ref_doc.employee,
|
||||
employee_name=ref_doc.employee_name,
|
||||
leave_type=ref_doc.leave_type,
|
||||
transaction_type=ref_doc.doctype,
|
||||
transaction_name=ref_doc.name,
|
||||
is_carry_forward=0,
|
||||
is_expired=0,
|
||||
is_lwp=0
|
||||
)
|
||||
ledger.update(args)
|
||||
|
||||
if submit:
|
||||
frappe.get_doc(ledger).submit()
|
||||
else:
|
||||
delete_ledger_entry(ledger)
|
||||
|
||||
def delete_ledger_entry(ledger):
|
||||
''' Delete ledger entry on cancel of leave application/allocation/encashment '''
|
||||
if ledger.transaction_type == "Leave Allocation":
|
||||
validate_leave_allocation_against_leave_application(ledger)
|
||||
|
||||
expired_entry = get_previous_expiry_ledger_entry(ledger)
|
||||
frappe.db.sql("""DELETE
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE
|
||||
`transaction_name`=%s
|
||||
OR `name`=%s""", (ledger.transaction_name, expired_entry))
|
||||
|
||||
def get_previous_expiry_ledger_entry(ledger):
|
||||
''' Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled '''
|
||||
creation_date = frappe.db.get_value("Leave Ledger Entry", filters={
|
||||
'transaction_name': ledger.transaction_name,
|
||||
'is_expired': 0,
|
||||
'transaction_type': 'Leave Allocation'
|
||||
}, fieldname=['creation'])
|
||||
|
||||
creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else ''
|
||||
|
||||
return frappe.db.get_value("Leave Ledger Entry", filters={
|
||||
'creation': ('like', creation_date+"%"),
|
||||
'employee': ledger.employee,
|
||||
'leave_type': ledger.leave_type,
|
||||
'is_expired': 1,
|
||||
'docstatus': 1,
|
||||
'is_carry_forward': 0
|
||||
}, fieldname=['name'])
|
||||
|
||||
def process_expired_allocation():
|
||||
''' Check if a carry forwarded allocation has expired and create a expiry ledger entry '''
|
||||
|
||||
# fetch leave type records that has carry forwarded leaves expiry
|
||||
leave_type_records = frappe.db.get_values("Leave Type", filters={
|
||||
'expire_carry_forwarded_leaves_after_days': (">", 0)
|
||||
}, fieldname=['name'])
|
||||
|
||||
if leave_type_records:
|
||||
leave_type = [record[0] for record in leave_type_records]
|
||||
|
||||
expired_allocation = frappe.db.sql_list("""SELECT name
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE
|
||||
`transaction_type`='Leave Allocation'
|
||||
AND `is_expired`=1""")
|
||||
|
||||
expire_allocation = frappe.get_all("Leave Ledger Entry",
|
||||
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
|
||||
filters={
|
||||
'to_date': ("<", today()),
|
||||
'transaction_type': 'Leave Allocation',
|
||||
'transaction_name': ('not in', expired_allocation)
|
||||
},
|
||||
or_filters={
|
||||
'is_carry_forward': 0,
|
||||
'leave_type': ('in', leave_type)
|
||||
})
|
||||
|
||||
if expire_allocation:
|
||||
create_expiry_ledger_entry(expire_allocation)
|
||||
|
||||
def create_expiry_ledger_entry(allocations):
|
||||
''' Create ledger entry for expired allocation '''
|
||||
for allocation in allocations:
|
||||
if allocation.is_carry_forward:
|
||||
expire_carried_forward_allocation(allocation)
|
||||
else:
|
||||
expire_allocation(allocation)
|
||||
|
||||
def get_remaining_leaves(allocation):
|
||||
''' Returns remaining leaves from the given allocation '''
|
||||
return frappe.db.get_value("Leave Ledger Entry",
|
||||
filters={
|
||||
'employee': allocation.employee,
|
||||
'leave_type': allocation.leave_type,
|
||||
'to_date': ('<=', allocation.to_date),
|
||||
}, fieldname=['SUM(leaves)'])
|
||||
|
||||
@frappe.whitelist()
|
||||
def expire_allocation(allocation, expiry_date=None):
|
||||
''' expires non-carry forwarded allocation '''
|
||||
leaves = get_remaining_leaves(allocation)
|
||||
expiry_date = expiry_date if expiry_date else allocation.to_date
|
||||
|
||||
if leaves:
|
||||
args = dict(
|
||||
leaves=flt(leaves) * -1,
|
||||
transaction_name=allocation.name,
|
||||
transaction_type='Leave Allocation',
|
||||
from_date=expiry_date,
|
||||
to_date=expiry_date,
|
||||
is_carry_forward=0,
|
||||
is_expired=1
|
||||
)
|
||||
create_leave_ledger_entry(allocation, args)
|
||||
|
||||
frappe.db.set_value("Leave Allocation", allocation.name, "expired", 1)
|
||||
|
||||
def expire_carried_forward_allocation(allocation):
|
||||
''' Expires remaining leaves in the on carried forward allocation '''
|
||||
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
|
||||
leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date)
|
||||
leaves = flt(allocation.leaves) + flt(leaves_taken)
|
||||
if leaves > 0:
|
||||
args = frappe._dict(
|
||||
transaction_name=allocation.name,
|
||||
transaction_type="Leave Allocation",
|
||||
leaves=allocation.leaves * -1,
|
||||
is_carry_forward=allocation.is_carry_forward,
|
||||
is_expired=1,
|
||||
from_date=allocation.to_date,
|
||||
to_date=allocation.to_date
|
||||
)
|
||||
create_leave_ledger_entry(allocation, args)
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestLeaveLedgerEntry(unittest.TestCase):
|
||||
pass
|
@ -68,7 +68,7 @@ frappe.ui.form.on('Leave Period', {
|
||||
},
|
||||
{
|
||||
"label": "Add unused leaves from previous allocations",
|
||||
"fieldname": "carry_forward_leaves",
|
||||
"fieldname": "carry_forward",
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
],
|
||||
|
@ -1,294 +1,294 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "HR-LPR-.YYYY.-.#####",
|
||||
"beta": 0,
|
||||
"creation": "2018-04-13 15:20:52.864288",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "HR-LPR-.YYYY.-.#####",
|
||||
"beta": 0,
|
||||
"creation": "2018-04-13 15:20:52.864288",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "From Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "From Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"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": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "To Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "To Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"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": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"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,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "is_active",
|
||||
"fieldtype": "Check",
|
||||
"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": "Is Active",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"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,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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": "is_active",
|
||||
"fieldtype": "Check",
|
||||
"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": "Is Active",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"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": "optional_holiday_list",
|
||||
"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": "Holiday List for Optional Leave",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Holiday List",
|
||||
"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,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "optional_holiday_list",
|
||||
"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": "Holiday List for Optional Leave",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Holiday List",
|
||||
"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
|
||||
}
|
||||
],
|
||||
"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-08-21 16:15:43.305502",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Period",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"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": "2019-05-30 16:15:43.305502",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Period",
|
||||
"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,
|
||||
"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,
|
||||
"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,
|
||||
"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,
|
||||
],
|
||||
"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
|
||||
}
|
@ -5,9 +5,10 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import getdate, cstr
|
||||
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.doctype.leave_allocation.leave_allocation import get_carry_forwarded_leaves
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from six import iteritems
|
||||
|
||||
@ -21,8 +22,8 @@ class LeavePeriod(Document):
|
||||
|
||||
condition_str = " and " + " and ".join(conditions) if len(conditions) else ""
|
||||
|
||||
employees = frappe.db.sql_list("select name from tabEmployee where status='Active' {condition}"
|
||||
.format(condition=condition_str), tuple(values))
|
||||
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
|
||||
|
||||
@ -36,29 +37,29 @@ class LeavePeriod(Document):
|
||||
|
||||
|
||||
def grant_leave_allocation(self, grade=None, department=None, designation=None,
|
||||
employee=None, carry_forward_leaves=0):
|
||||
employees = self.get_employees({
|
||||
employee=None, carry_forward=0):
|
||||
employee_records = self.get_employees({
|
||||
"grade": grade,
|
||||
"department": department,
|
||||
"designation": designation,
|
||||
"department": department,
|
||||
"designation": designation,
|
||||
"name": employee
|
||||
})
|
||||
|
||||
if employees:
|
||||
if len(employees) > 20:
|
||||
if employee_records:
|
||||
if len(employee_records) > 20:
|
||||
frappe.enqueue(grant_leave_alloc_for_employees, timeout=600,
|
||||
employees=employees, leave_period=self, carry_forward_leaves=carry_forward_leaves)
|
||||
employee_records=employee_records, leave_period=self, carry_forward=carry_forward)
|
||||
else:
|
||||
grant_leave_alloc_for_employees(employees, self, carry_forward_leaves)
|
||||
grant_leave_alloc_for_employees(employee_records, self, carry_forward)
|
||||
else:
|
||||
frappe.msgprint(_("No Employee Found"))
|
||||
|
||||
def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leaves=0):
|
||||
def grant_leave_alloc_for_employees(employee_records, leave_period, carry_forward=0):
|
||||
leave_allocations = []
|
||||
existing_allocations_for = get_existing_allocations(employees, leave_period.name)
|
||||
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 employees:
|
||||
count = 0
|
||||
for employee in employee_records.keys():
|
||||
if employee in existing_allocations_for:
|
||||
continue
|
||||
count +=1
|
||||
@ -67,18 +68,24 @@ def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leave
|
||||
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_leaves)
|
||||
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(employees) - set(existing_allocations_for)), title = _("Allocating leaves..."))
|
||||
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 docstatus=1
|
||||
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}")
|
||||
@ -87,28 +94,36 @@ def get_existing_allocations(employees, leave_period):
|
||||
|
||||
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"])
|
||||
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_leaves):
|
||||
allocation = frappe.new_doc("Leave Allocation")
|
||||
allocation.employee = employee
|
||||
allocation.leave_type = leave_type
|
||||
allocation.from_date = leave_period.from_date
|
||||
allocation.to_date = leave_period.to_date
|
||||
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.new_leaves_allocated = new_leaves_allocated
|
||||
allocation.leave_period = leave_period.name
|
||||
if carry_forward_leaves:
|
||||
if leave_type_details.get(leave_type).is_carry_forward:
|
||||
allocation.carry_forward = carry_forward_leaves
|
||||
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
|
||||
|
||||
|
||||
return allocation.name
|
@ -12,6 +12,9 @@ def get_data():
|
||||
},
|
||||
{
|
||||
'items': ['Employee Grade']
|
||||
}
|
||||
},
|
||||
{
|
||||
'items': ['Leave Allocation']
|
||||
},
|
||||
]
|
||||
}
|
@ -12,16 +12,20 @@ class TestLeavePolicy(unittest.TestCase):
|
||||
if random_leave_type:
|
||||
random_leave_type = random_leave_type[0]
|
||||
leave_type = frappe.get_doc("Leave Type", random_leave_type.name)
|
||||
old_max_leaves_allowed = leave_type.max_leaves_allowed
|
||||
leave_type.max_leaves_allowed = 2
|
||||
leave_type.save()
|
||||
|
||||
leave_policy_details = {
|
||||
"doctype": "Leave Policy",
|
||||
"leave_policy_details": [{
|
||||
"leave_type": leave_type.name,
|
||||
"annual_allocation": leave_type.max_leaves_allowed + 1
|
||||
}]
|
||||
}
|
||||
leave_policy = create_leave_policy(leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, frappe.get_doc(leave_policy_details).insert)
|
||||
self.assertRaises(frappe.ValidationError, leave_policy.insert)
|
||||
|
||||
def create_leave_policy(**args):
|
||||
''' Returns an object of leave policy '''
|
||||
args = frappe._dict(args)
|
||||
return frappe.get_doc({
|
||||
"doctype": "Leave Policy",
|
||||
"leave_policy_details": [{
|
||||
"leave_type": args.leave_type or "_Test Leave Type",
|
||||
"annual_allocation": args.annual_allocation or 10
|
||||
}]
|
||||
})
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
@ -14,10 +15,12 @@
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "leave_type_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -42,15 +45,17 @@
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "max_leaves_allowed",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
@ -78,10 +83,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "applicable_after",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
@ -109,10 +116,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "max_continuous_days_allowed",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
@ -141,10 +150,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
@ -171,10 +182,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "is_carry_forward",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -203,10 +216,13 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "is_lwp",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -233,10 +249,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "is_optional_leave",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -264,10 +282,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "allow_negative",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -294,10 +314,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "include_holiday",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -324,10 +346,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "is_compensatory",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -355,10 +379,81 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: doc.is_carry_forward == 1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "carry_forward_section",
|
||||
"fieldtype": "Section Break",
|
||||
"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": "Carry Forward",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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,
|
||||
"depends_on": "",
|
||||
"description": "Calculated in days",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "expire_carry_forwarded_leaves_after_days",
|
||||
"fieldtype": "Int",
|
||||
"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": "Expire Carry Forwarded Leaves (Days)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"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": 1,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "encashment",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@ -386,10 +481,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "allow_encashment",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -417,11 +514,13 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "allow_encashment",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "encashment_threshold_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
@ -449,11 +548,13 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "allow_encashment",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "earning_component",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@ -482,10 +583,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "earned_leave",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@ -513,10 +616,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "is_earned_leave",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -544,11 +649,13 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "is_earned_leave",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "earned_leave_frequency",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@ -577,12 +684,14 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.5",
|
||||
"depends_on": "is_earned_leave",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "rounding",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@ -611,17 +720,15 @@
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-flag",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-06-03 18:32:51.803472",
|
||||
"modified": "2019-08-02 15:38:39.334283",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Type",
|
||||
@ -687,8 +794,8 @@
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
}
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
@ -2,9 +2,22 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import calendar
|
||||
import frappe
|
||||
from datetime import datetime
|
||||
from frappe.utils import today
|
||||
from frappe import _
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
class LeaveType(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
if self.is_lwp:
|
||||
leave_allocation = frappe.get_all("Leave Allocation", filters={
|
||||
'leave_type': self.name,
|
||||
'from_date': ("<=", today()),
|
||||
'to_date': (">=", today())
|
||||
}, fields=['name'])
|
||||
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
|
||||
|
@ -2,6 +2,25 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
import frappe
|
||||
test_records = frappe.get_test_records('Leave Type')
|
||||
from frappe import _
|
||||
|
||||
test_records = frappe.get_test_records('Leave Type')
|
||||
|
||||
def create_leave_type(**args):
|
||||
args = frappe._dict(args)
|
||||
if frappe.db.exists("Leave Type", args.leave_type_name):
|
||||
return frappe.get_doc("Leave Type", args.leave_type_name)
|
||||
leave_type = frappe.get_doc({
|
||||
"doctype": "Leave Type",
|
||||
"leave_type_name": args.leave_type_name or "_Test Leave Type",
|
||||
"include_holiday": args.include_holidays or 1,
|
||||
"allow_encashment": args.allow_encashment or 0,
|
||||
"is_earned_leave": args.is_earned_leave or 0,
|
||||
"is_lwp": args.is_lwp 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"
|
||||
})
|
||||
return leave_type
|
@ -69,7 +69,7 @@ frappe.ui.form.on('Payroll Entry', {
|
||||
},
|
||||
|
||||
add_context_buttons: function(frm) {
|
||||
if(frm.doc.salary_slips_submitted) {
|
||||
if(frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
|
||||
frm.events.add_bank_entry_button(frm);
|
||||
} else if(frm.doc.salary_slips_created) {
|
||||
frm.add_custom_button(__("Submit Salary Slip"), function() {
|
||||
|
@ -13,14 +13,13 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
|
||||
class PayrollEntry(Document):
|
||||
def onload(self):
|
||||
if not self.docstatus==1:
|
||||
return
|
||||
if not self.docstatus==1 or self.salary_slips_submitted:
|
||||
return
|
||||
|
||||
# check if salary slips were manually submitted
|
||||
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
|
||||
if cint(entries) == len(self.employees) and not self.salary_slips_submitted:
|
||||
self.db_set("salary_slips_submitted", 1)
|
||||
self.reload()
|
||||
if cint(entries) == len(self.employees):
|
||||
self.set_onload("submitted_ss", True)
|
||||
|
||||
def on_submit(self):
|
||||
self.create_salary_slips()
|
||||
@ -429,7 +428,6 @@ def get_start_end_dates(payroll_frequency, start_date=None, company=None):
|
||||
'start_date': start_date, 'end_date': end_date
|
||||
})
|
||||
|
||||
|
||||
def get_frequency_kwargs(frequency_name):
|
||||
frequency_dict = {
|
||||
'monthly': {'months': 1},
|
||||
|
@ -23,14 +23,9 @@
|
||||
"grace_period_settings_auto_attendance_section",
|
||||
"enable_entry_grace_period",
|
||||
"late_entry_grace_period",
|
||||
"consequence_after",
|
||||
"consequence",
|
||||
"column_break_18",
|
||||
"enable_exit_grace_period",
|
||||
"enable_different_consequence_for_early_exit",
|
||||
"early_exit_grace_period",
|
||||
"early_exit_consequence_after",
|
||||
"early_exit_consequence"
|
||||
"early_exit_grace_period"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -107,21 +102,6 @@
|
||||
"fieldtype": "Int",
|
||||
"label": "Late Entry Grace Period"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_entry_grace_period",
|
||||
"description": "The number of occurrence after which the consequence is executed.",
|
||||
"fieldname": "consequence_after",
|
||||
"fieldtype": "Int",
|
||||
"label": "Consequence after"
|
||||
},
|
||||
{
|
||||
"default": "Half Day",
|
||||
"depends_on": "enable_entry_grace_period",
|
||||
"fieldname": "consequence",
|
||||
"fieldtype": "Select",
|
||||
"label": "Consequence",
|
||||
"options": "Half Day\nAbsent"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
@ -132,13 +112,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Exit Grace Period"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "enable_exit_grace_period",
|
||||
"fieldname": "enable_different_consequence_for_early_exit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Different Consequence for Early Exit"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_exit_grace_period",
|
||||
"description": "The time before the shift end time when check-out is considered as early (in minutes).",
|
||||
@ -146,21 +119,6 @@
|
||||
"fieldtype": "Int",
|
||||
"label": "Early Exit Grace Period"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_exit_grace_period && doc.enable_different_consequence_for_early_exit",
|
||||
"description": "The number of occurrence after which the consequence is executed.",
|
||||
"fieldname": "early_exit_consequence_after",
|
||||
"fieldtype": "Int",
|
||||
"label": "Early Exit Consequence after"
|
||||
},
|
||||
{
|
||||
"default": "Half Day",
|
||||
"depends_on": "eval:doc.enable_exit_grace_period && doc.enable_different_consequence_for_early_exit",
|
||||
"fieldname": "early_exit_consequence",
|
||||
"fieldtype": "Select",
|
||||
"label": "Early Exit Consequence",
|
||||
"options": "Half Day\nAbsent"
|
||||
},
|
||||
{
|
||||
"default": "60",
|
||||
"description": "Time after the end of shift during which check-out is considered for attendance.",
|
||||
@ -178,7 +136,6 @@
|
||||
"depends_on": "enable_auto_attendance",
|
||||
"fieldname": "grace_period_settings_auto_attendance_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1,
|
||||
"label": "Grace Period Settings For Auto Attendance"
|
||||
},
|
||||
{
|
||||
@ -201,7 +158,7 @@
|
||||
"label": "Last Sync of Checkin"
|
||||
}
|
||||
],
|
||||
"modified": "2019-06-10 06:02:44.272036",
|
||||
"modified": "2019-07-30 01:05:24.660666",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Shift Type",
|
||||
|
@ -28,8 +28,8 @@ class ShiftType(Document):
|
||||
logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time")
|
||||
for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])):
|
||||
single_shift_logs = list(group)
|
||||
attendance_status, working_hours = self.get_attendance(single_shift_logs)
|
||||
mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, self.name)
|
||||
attendance_status, working_hours, late_entry, early_exit = self.get_attendance(single_shift_logs)
|
||||
mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, self.name)
|
||||
for employee in self.get_assigned_employee(self.process_attendance_after, True):
|
||||
self.mark_absent_for_dates_with_no_attendance(employee)
|
||||
|
||||
@ -39,12 +39,19 @@ class ShiftType(Document):
|
||||
1. These logs belongs to an single shift, single employee and is not in a holiday date.
|
||||
2. Logs are in chronological order
|
||||
"""
|
||||
total_working_hours = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on)
|
||||
late_entry = early_exit = False
|
||||
total_working_hours, in_time, out_time = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on)
|
||||
if cint(self.enable_entry_grace_period) and in_time and in_time > logs[0].shift_start + timedelta(minutes=cint(self.late_entry_grace_period)):
|
||||
late_entry = True
|
||||
|
||||
if cint(self.enable_exit_grace_period) and out_time and out_time < logs[0].shift_end - timedelta(minutes=cint(self.early_exit_grace_period)):
|
||||
early_exit = True
|
||||
|
||||
if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
|
||||
return 'Absent', total_working_hours
|
||||
return 'Absent', total_working_hours, late_entry, early_exit
|
||||
if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
|
||||
return 'Half Day', total_working_hours
|
||||
return 'Present', total_working_hours
|
||||
return 'Half Day', total_working_hours, late_entry, early_exit
|
||||
return 'Present', total_working_hours, late_entry, early_exit
|
||||
|
||||
def mark_absent_for_dates_with_no_attendance(self, employee):
|
||||
"""Marks Absents for the given employee on working days in this shift which have no attendance marked.
|
||||
|
@ -24,6 +24,18 @@ frappe.query_reports["Employee Leave Balance"] = {
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
},
|
||||
{
|
||||
"fieldname":"department",
|
||||
"label": __("Department"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Department",
|
||||
},
|
||||
{
|
||||
"fieldname":"employee",
|
||||
"label": __("Employee"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Employee",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -4,8 +4,9 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from erpnext.hr.doctype.leave_application.leave_application \
|
||||
import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period
|
||||
import get_leave_balance_on, get_leaves_for_period
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@ -30,17 +31,28 @@ def get_columns(leave_types):
|
||||
|
||||
return columns
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = {
|
||||
"status": "Active",
|
||||
"company": filters.company,
|
||||
}
|
||||
if filters.get("department"):
|
||||
conditions.update({"department": filters.get("department")})
|
||||
if filters.get("employee"):
|
||||
conditions.update({"employee": filters.get("employee")})
|
||||
|
||||
return conditions
|
||||
|
||||
def get_data(filters, leave_types):
|
||||
user = frappe.session.user
|
||||
allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date)
|
||||
allocation_records_based_on_from_date = get_leave_allocation_records(filters.from_date)
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
if filters.to_date <= filters.from_date:
|
||||
frappe.throw(_("From date can not be greater than than To date"))
|
||||
|
||||
active_employees = frappe.get_all("Employee",
|
||||
filters = { "status": "Active", "company": filters.company},
|
||||
fields = ["name", "employee_name", "department", "user_id"])
|
||||
filters=conditions,
|
||||
fields=["name", "employee_name", "department", "user_id"])
|
||||
|
||||
data = []
|
||||
for employee in active_employees:
|
||||
@ -50,16 +62,14 @@ def get_data(filters, leave_types):
|
||||
|
||||
for leave_type in leave_types:
|
||||
# leaves taken
|
||||
leaves_taken = get_approved_leaves_for_period(employee.name, leave_type,
|
||||
filters.from_date, filters.to_date)
|
||||
leaves_taken = get_leaves_for_period(employee.name, leave_type,
|
||||
filters.from_date, filters.to_date) * -1
|
||||
|
||||
# opening balance
|
||||
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date,
|
||||
allocation_records_based_on_to_date.get(employee.name, frappe._dict()))
|
||||
opening = get_total_allocated_leaves(employee.name, leave_type, filters.from_date, filters.to_date)
|
||||
|
||||
# closing balance
|
||||
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date,
|
||||
allocation_records_based_on_to_date.get(employee.name, frappe._dict()))
|
||||
closing = flt(opening) - flt(leaves_taken)
|
||||
|
||||
row += [opening, leaves_taken, closing]
|
||||
|
||||
@ -84,3 +94,18 @@ def get_approvers(department):
|
||||
where parent = %s and parentfield = 'leave_approvers'""", (d), as_dict=True)])
|
||||
|
||||
return approvers
|
||||
|
||||
def get_total_allocated_leaves(employee, leave_type, from_date, to_date):
|
||||
''' Returns leave allocation between from date and to date '''
|
||||
leave_allocation_records = frappe.db.get_all('Leave Ledger Entry', filters={
|
||||
'docstatus': 1,
|
||||
'is_expired': 0,
|
||||
'leave_type': leave_type,
|
||||
'employee': employee,
|
||||
'transaction_type': 'Leave Allocation'
|
||||
}, or_filters={
|
||||
'from_date': ['between', (from_date, to_date)],
|
||||
'to_date': ['between', (from_date, to_date)]
|
||||
}, fields=['SUM(leaves) as leaves'])
|
||||
|
||||
return flt(leave_allocation_records[0].get('leaves')) if leave_allocation_records else flt(0)
|
@ -25,6 +25,7 @@ def execute(filters=None):
|
||||
leave_types = frappe.db.sql("""select name from `tabLeave Type`""", as_list=True)
|
||||
leave_list = [d[0] for d in leave_types]
|
||||
columns.extend(leave_list)
|
||||
columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"])
|
||||
|
||||
for emp in sorted(att_map):
|
||||
emp_det = emp_map.get(emp)
|
||||
@ -65,6 +66,10 @@ def execute(filters=None):
|
||||
|
||||
leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\
|
||||
where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1)
|
||||
|
||||
time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \
|
||||
late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \
|
||||
early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters)
|
||||
|
||||
leaves = {}
|
||||
for d in leave_details:
|
||||
@ -80,7 +85,8 @@ def execute(filters=None):
|
||||
row.append(leaves[d])
|
||||
else:
|
||||
row.append("0.0")
|
||||
|
||||
|
||||
row.extend([time_default_counts[0][0],time_default_counts[0][1]])
|
||||
data.append(row)
|
||||
return columns, data
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr
|
||||
from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr, add_days, today
|
||||
from frappe.model.document import Document
|
||||
from frappe.desk.form import assign_to
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
@ -36,12 +36,18 @@ class EmployeeBoardingController(Document):
|
||||
}).insert(ignore_permissions=True)
|
||||
self.db_set("project", project.name)
|
||||
self.db_set("boarding_status", "Pending")
|
||||
self.reload()
|
||||
self.create_task_and_notify_user()
|
||||
|
||||
def create_task_and_notify_user(self):
|
||||
# create the task for the given project and assign to the concerned person
|
||||
for activity in self.activities:
|
||||
if activity.task:
|
||||
continue
|
||||
|
||||
task = frappe.get_doc({
|
||||
"doctype": "Task",
|
||||
"project": project.name,
|
||||
"project": self.project,
|
||||
"subject": activity.activity_name + " : " + self.employee_name,
|
||||
"description": activity.description,
|
||||
"department": self.department,
|
||||
@ -69,6 +75,7 @@ class EmployeeBoardingController(Document):
|
||||
'doctype' : task.doctype,
|
||||
'name' : task.name,
|
||||
'description' : task.description or task.subject,
|
||||
'notify': self.notify_users_by_email
|
||||
}
|
||||
assign_to.add(args)
|
||||
|
||||
@ -263,6 +270,21 @@ def get_leave_period(from_date, to_date, company):
|
||||
if leave_period:
|
||||
return leave_period
|
||||
|
||||
def generate_leave_encashment():
|
||||
''' Generates a draft leave encashment on allocation expiry '''
|
||||
from erpnext.hr.doctype.leave_encashment.leave_encashment import create_leave_encashment
|
||||
|
||||
if frappe.db.get_single_value('HR Settings', 'auto_leave_encashment'):
|
||||
leave_type = frappe.get_all('Leave Type', filters={'allow_encashment': 1}, fields=['name'])
|
||||
leave_type=[l['name'] for l in leave_type]
|
||||
|
||||
leave_allocation = frappe.get_all("Leave Allocation", filters={
|
||||
'to_date': add_days(today(), -1),
|
||||
'leave_type': ('in', leave_type)
|
||||
}, fields=['employee', 'leave_period', 'leave_type', 'to_date', 'total_leaves_allocated', 'new_leaves_allocated'])
|
||||
|
||||
create_leave_encashment(leave_allocation=leave_allocation)
|
||||
|
||||
def allocate_earned_leaves():
|
||||
'''Allocate earned leaves to Employees'''
|
||||
e_leave_types = frappe.get_all("Leave Type",
|
||||
@ -270,31 +292,43 @@ def allocate_earned_leaves():
|
||||
filters={'is_earned_leave' : 1})
|
||||
today = getdate()
|
||||
divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
|
||||
if e_leave_types:
|
||||
for e_leave_type in e_leave_types:
|
||||
leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where '{0}'
|
||||
between from_date and to_date and docstatus=1 and leave_type='{1}'"""
|
||||
.format(today, e_leave_type.name), as_dict=1)
|
||||
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):
|
||||
continue
|
||||
annual_allocation = frappe.db.sql("""select annual_allocation from `tabLeave Policy Detail`
|
||||
where parent=%s and leave_type=%s""", (leave_policy.name, e_leave_type.name))
|
||||
if annual_allocation and annual_allocation[0]:
|
||||
earned_leaves = flt(annual_allocation[0][0]) / 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)
|
||||
|
||||
allocated_leaves = frappe.db.get_value('Leave Allocation', allocation.name, 'total_leaves_allocated')
|
||||
new_allocation = flt(allocated_leaves) + flt(earned_leaves)
|
||||
new_allocation = new_allocation if new_allocation <= e_leave_type.max_leaves_allowed else e_leave_type.max_leaves_allowed
|
||||
frappe.db.set_value('Leave Allocation', allocation.name, 'total_leaves_allocated', new_allocation)
|
||||
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)
|
||||
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):
|
||||
continue
|
||||
annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={
|
||||
'parent': leave_policy.name,
|
||||
'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)
|
||||
new_allocation = new_allocation if new_allocation <= e_leave_type.max_leaves_allowed else e_leave_type.max_leaves_allowed
|
||||
|
||||
if new_allocation == allocation.total_leaves_allocated:
|
||||
continue
|
||||
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
|
||||
create_earned_leave_ledger_entry(allocation, earned_leaves, today)
|
||||
|
||||
def create_earned_leave_ledger_entry(allocation, earned_leaves, date):
|
||||
''' Create leave ledger entry based on the earned leave frequency '''
|
||||
allocation.new_leaves_allocated = earned_leaves
|
||||
allocation.from_date = 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'''
|
||||
|
@ -626,3 +626,4 @@ erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
|
||||
erpnext.patches.v12_0.update_ewaybill_field_position
|
||||
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes
|
||||
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
|
||||
erpnext.patches.v12_0.generate_leave_ledger_entries
|
87
erpnext/patches/v12_0/generate_leave_ledger_entries.py
Normal file
87
erpnext/patches/v12_0/generate_leave_ledger_entries.py
Normal file
@ -0,0 +1,87 @@
|
||||
# Copyright (c) 2018, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import getdate, today
|
||||
|
||||
def execute():
|
||||
""" Generates leave ledger entries for leave allocation/application/encashment
|
||||
for last allocation """
|
||||
frappe.reload_doc("HR", "doctype", "Leave Ledger Entry")
|
||||
frappe.reload_doc("HR", "doctype", "Leave Encashment")
|
||||
if frappe.db.a_row_exists("Leave Ledger Entry"):
|
||||
return
|
||||
|
||||
if not frappe.get_meta("Leave Allocation").has_field("unused_leaves"):
|
||||
frappe.reload_doc("HR", "doctype", "Leave Allocation")
|
||||
update_leave_allocation_fieldname()
|
||||
|
||||
generate_allocation_ledger_entries()
|
||||
generate_application_leave_ledger_entries()
|
||||
generate_encashment_leave_ledger_entries()
|
||||
generate_expiry_allocation_ledger_entries()
|
||||
|
||||
def update_leave_allocation_fieldname():
|
||||
''' maps data from old field to the new field '''
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabLeave Allocation`
|
||||
SET `unused_leaves` = `carry_forwarded_leaves`
|
||||
""")
|
||||
|
||||
def generate_allocation_ledger_entries():
|
||||
''' fix ledger entries for missing leave allocation transaction '''
|
||||
allocation_list = get_allocation_records()
|
||||
|
||||
for allocation in allocation_list:
|
||||
if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name}):
|
||||
allocation.update(dict(doctype="Leave Allocation"))
|
||||
allocation_obj = frappe.get_doc(allocation)
|
||||
allocation_obj.create_leave_ledger_entry()
|
||||
|
||||
def generate_application_leave_ledger_entries():
|
||||
''' fix ledger entries for missing leave application transaction '''
|
||||
leave_applications = get_leaves_application_records()
|
||||
|
||||
for application in leave_applications:
|
||||
if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': application.name}):
|
||||
application.update(dict(doctype="Leave Application"))
|
||||
frappe.get_doc(application).create_leave_ledger_entry()
|
||||
|
||||
def generate_encashment_leave_ledger_entries():
|
||||
''' fix ledger entries for missing leave encashment transaction '''
|
||||
leave_encashments = get_leave_encashment_records()
|
||||
|
||||
for encashment in leave_encashments:
|
||||
if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': encashment.name}):
|
||||
encashment.update(dict(doctype="Leave Encashment"))
|
||||
frappe.get_doc(encashment).create_leave_ledger_entry()
|
||||
|
||||
def generate_expiry_allocation_ledger_entries():
|
||||
''' fix ledger entries for missing leave allocation transaction '''
|
||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation
|
||||
allocation_list = get_allocation_records()
|
||||
|
||||
for allocation in allocation_list:
|
||||
if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name, 'is_expired': 1}):
|
||||
allocation.update(dict(doctype="Leave Allocation"))
|
||||
allocation_obj = frappe.get_doc(allocation)
|
||||
if allocation_obj.to_date <= getdate(today()):
|
||||
expire_allocation(allocation_obj)
|
||||
|
||||
def get_allocation_records():
|
||||
return frappe.get_all("Leave Allocation", filters={
|
||||
"docstatus": 1
|
||||
}, fields=['name', 'employee', 'leave_type', 'new_leaves_allocated',
|
||||
'unused_leaves', 'from_date', 'to_date', 'carry_forward'
|
||||
], order_by='to_date ASC')
|
||||
|
||||
def get_leaves_application_records():
|
||||
return frappe.get_all("Leave Application", filters={
|
||||
"docstatus": 1
|
||||
}, fields=['name', 'employee', 'leave_type', 'total_leave_days', 'from_date', 'to_date'])
|
||||
|
||||
def get_leave_encashment_records():
|
||||
return frappe.get_all("Leave Encashment", filters={
|
||||
"docstatus": 1
|
||||
}, fields=['name', 'employee', 'leave_type', 'encashable_days', 'encashment_date'])
|
@ -164,7 +164,7 @@ class Task(NestedSet):
|
||||
if self.status not in ('Cancelled', 'Completed') and self.exp_end_date:
|
||||
from datetime import datetime
|
||||
if self.exp_end_date < datetime.now().date():
|
||||
self.db_set('status', 'Overdue')
|
||||
self.db_set('status', 'Overdue', update_modified=False)
|
||||
self.update_project()
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -11,12 +11,51 @@ class CallPopup {
|
||||
'static': true,
|
||||
'minimizable': true,
|
||||
'fields': [{
|
||||
'fieldname': 'caller_info',
|
||||
'fieldtype': 'HTML'
|
||||
'fieldname': 'name',
|
||||
'label': 'Name',
|
||||
'default': this.get_caller_name() || __('Unknown Caller'),
|
||||
'fieldtype': 'Data',
|
||||
'read_only': 1
|
||||
}, {
|
||||
'fieldtype': 'Button',
|
||||
'label': __('Open Contact'),
|
||||
'click': () => frappe.set_route('Form', 'Contact', this.call_log.contact),
|
||||
'depends_on': () => this.call_log.contact
|
||||
}, {
|
||||
'fieldtype': 'Button',
|
||||
'label': __('Open Lead'),
|
||||
'click': () => frappe.set_route('Form', 'Lead', this.call_log.lead),
|
||||
'depends_on': () => this.call_log.lead
|
||||
}, {
|
||||
'fieldtype': 'Button',
|
||||
'label': __('Make New Contact'),
|
||||
'click': () => frappe.new_doc('Contact', { 'mobile_no': this.caller_number }),
|
||||
'depends_on': () => !this.get_caller_name()
|
||||
}, {
|
||||
'fieldtype': 'Button',
|
||||
'label': __('Make New Lead'),
|
||||
'click': () => frappe.new_doc('Lead', { 'mobile_no': this.caller_number }),
|
||||
'depends_on': () => !this.get_caller_name()
|
||||
}, {
|
||||
'fieldtype': 'Column Break',
|
||||
}, {
|
||||
'fieldname': 'number',
|
||||
'label': 'Phone Number',
|
||||
'fieldtype': 'Data',
|
||||
'default': this.caller_number,
|
||||
'read_only': 1
|
||||
}, {
|
||||
'fielname': 'last_interaction',
|
||||
'fieldtype': 'Section Break',
|
||||
'label': __('Activity'),
|
||||
'depends_on': () => this.get_caller_name()
|
||||
}, {
|
||||
'fieldtype': 'Small Text',
|
||||
'label': __('Last Issue'),
|
||||
'fieldname': 'last_issue',
|
||||
'read_only': true,
|
||||
'depends_on': () => this.call_log.contact,
|
||||
'default': `<i class="text-muted">${__('No issue has been raised by the caller.')}<i>`
|
||||
}, {
|
||||
'fieldtype': 'Small Text',
|
||||
'label': __('Last Communication'),
|
||||
@ -24,13 +63,7 @@ class CallPopup {
|
||||
'read_only': true,
|
||||
'default': `<i class="text-muted">${__('No communication found.')}<i>`
|
||||
}, {
|
||||
'fieldtype': 'Small Text',
|
||||
'label': __('Last Issue'),
|
||||
'fieldname': 'last_issue',
|
||||
'read_only': true,
|
||||
'default': `<i class="text-muted">${__('No issue raised by the customer.')}<i>`
|
||||
}, {
|
||||
'fieldtype': 'Column Break',
|
||||
'fieldtype': 'Section Break',
|
||||
}, {
|
||||
'fieldtype': 'Small Text',
|
||||
'label': __('Call Summary'),
|
||||
@ -41,13 +74,21 @@ class CallPopup {
|
||||
'click': () => {
|
||||
const call_summary = this.dialog.get_value('call_summary');
|
||||
if (!call_summary) return;
|
||||
frappe.xcall('erpnext.crm.doctype.utils.add_call_summary', {
|
||||
'docname': this.call_log.id,
|
||||
frappe.xcall('erpnext.communication.doctype.call_log.call_log.add_call_summary', {
|
||||
'call_log': this.call_log.name,
|
||||
'summary': call_summary,
|
||||
}).then(() => {
|
||||
this.close_modal();
|
||||
frappe.show_alert({
|
||||
message: `${__('Call Summary Saved')}<br><a class="text-small text-muted" href="#Form/Call Log/${this.call_log.name}">${__('View call log')}</a>`,
|
||||
message: `
|
||||
${__('Call Summary Saved')}
|
||||
<br>
|
||||
<a
|
||||
class="text-small text-muted"
|
||||
href="#Form/Call Log/${this.call_log.name}">
|
||||
${__('View call log')}
|
||||
</a>
|
||||
`,
|
||||
indicator: 'green'
|
||||
});
|
||||
});
|
||||
@ -55,71 +96,14 @@ class CallPopup {
|
||||
}],
|
||||
});
|
||||
this.set_call_status();
|
||||
this.make_caller_info_section();
|
||||
this.dialog.get_close_btn().show();
|
||||
this.make_last_interaction_section();
|
||||
this.dialog.$body.addClass('call-popup');
|
||||
this.dialog.set_secondary_action(this.close_modal.bind(this));
|
||||
frappe.utils.play_sound('incoming-call');
|
||||
this.dialog.show();
|
||||
}
|
||||
|
||||
make_caller_info_section() {
|
||||
const wrapper = this.dialog.get_field('caller_info').$wrapper;
|
||||
wrapper.append(`<div class="text-muted"> ${__("Loading...")} </div>`);
|
||||
frappe.xcall('erpnext.crm.doctype.utils.get_document_with_phone_number', {
|
||||
'number': this.caller_number
|
||||
}).then(contact_doc => {
|
||||
wrapper.empty();
|
||||
const contact = this.contact = contact_doc;
|
||||
if (!contact) {
|
||||
this.setup_unknown_caller(wrapper);
|
||||
} else {
|
||||
this.setup_known_caller(wrapper);
|
||||
this.set_call_status();
|
||||
this.make_last_interaction_section();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setup_unknown_caller(wrapper) {
|
||||
wrapper.append(`
|
||||
<div class="caller-info">
|
||||
<b>${__('Unknown Number')}:</b> ${this.caller_number}
|
||||
<button
|
||||
class="margin-left btn btn-new btn-default btn-xs"
|
||||
data-doctype="Contact"
|
||||
title=${__("Make New Contact")}>
|
||||
<i class="octicon octicon-plus text-medium"></i>
|
||||
</button>
|
||||
</div>
|
||||
`).find('button').click(
|
||||
() => frappe.set_route(`Form/Contact/New Contact?phone=${this.caller_number}`)
|
||||
);
|
||||
}
|
||||
|
||||
setup_known_caller(wrapper) {
|
||||
const contact = this.contact;
|
||||
const contact_name = frappe.utils.get_form_link(contact.doctype, contact.name, true, this.get_caller_name());
|
||||
const links = contact.links ? contact.links : [];
|
||||
|
||||
let contact_links = '';
|
||||
|
||||
links.forEach(link => {
|
||||
contact_links += `<div>${link.link_doctype}: ${frappe.utils.get_form_link(link.link_doctype, link.link_name, true)}</div>`;
|
||||
});
|
||||
wrapper.append(`
|
||||
<div class="caller-info flex">
|
||||
${frappe.avatar(null, 'avatar-xl', contact.name, contact.image || '')}
|
||||
<div>
|
||||
<h5>${contact_name}</h5>
|
||||
<div>${contact.mobile_no || ''}</div>
|
||||
<div>${contact.phone_no || ''}</div>
|
||||
${contact_links}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
set_indicator(color, blink=false) {
|
||||
let classes = `indicator ${color} ${blink ? 'blink': ''}`;
|
||||
this.dialog.header.find('.indicator').attr('class', classes);
|
||||
@ -129,7 +113,7 @@ class CallPopup {
|
||||
let title = '';
|
||||
call_status = call_status || this.call_log.status;
|
||||
if (['Ringing'].includes(call_status) || !call_status) {
|
||||
title = __('Incoming call from {0}', [this.get_caller_name()]);
|
||||
title = __('Incoming call from {0}', [this.get_caller_name() || this.caller_number]);
|
||||
this.set_indicator('blue', true);
|
||||
} else if (call_status === 'In Progress') {
|
||||
title = __('Call Connected');
|
||||
@ -164,13 +148,13 @@ class CallPopup {
|
||||
if (!this.dialog.get_value('call_summary')) {
|
||||
this.close_modal();
|
||||
}
|
||||
}, 10000);
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
make_last_interaction_section() {
|
||||
frappe.xcall('erpnext.crm.doctype.utils.get_last_interaction', {
|
||||
'number': this.caller_number,
|
||||
'reference_doc': this.contact
|
||||
'contact': this.call_log.contact,
|
||||
'lead': this.call_log.lead
|
||||
}).then(data => {
|
||||
const comm_field = this.dialog.get_field('last_communication');
|
||||
if (data.last_communication) {
|
||||
@ -182,15 +166,20 @@ class CallPopup {
|
||||
const issue = data.last_issue;
|
||||
const issue_field = this.dialog.get_field("last_issue");
|
||||
issue_field.set_value(issue.subject);
|
||||
issue_field.$wrapper.append(`<a class="text-medium" href="#List/Issue?customer=${issue.customer}">
|
||||
${__('View all issues from {0}', [issue.customer])}
|
||||
</a>`);
|
||||
issue_field.$wrapper.append(`
|
||||
<a class="text-medium" href="#List/Issue?customer=${issue.customer}">
|
||||
${__('View all issues from {0}', [issue.customer])}
|
||||
</a>
|
||||
`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get_caller_name() {
|
||||
return this.contact ? this.contact.lead_name || this.contact.name || '' : this.caller_number;
|
||||
let log = this.call_log;
|
||||
return log.contact_name || log.lead_name;
|
||||
}
|
||||
|
||||
setup_listener() {
|
||||
frappe.realtime.on(`call_${this.call_log.id}_disconnected`, call_log => {
|
||||
this.call_disconnected(call_log);
|
||||
|
@ -1,12 +1,13 @@
|
||||
frappe.provide('frappe.ui.form');
|
||||
|
||||
erpnext.doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
|
||||
"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item",
|
||||
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item",
|
||||
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
|
||||
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
|
||||
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
|
||||
"Subscription Plan"];
|
||||
"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Shipping Rule", "Loyalty Program",
|
||||
"Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool",
|
||||
"Subscription", "Purchase Order", "Journal Entry", "Material Request", "Purchase Receipt", "Landed Cost Item", "Asset"];
|
||||
|
||||
erpnext.child_docs = ["Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account",
|
||||
"Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction",
|
||||
"Landed Cost Item", "Asset Value Adjustment", "Opening Invoice Creation Tool Item", "Subscription Plan"];
|
||||
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimension_filters",
|
||||
@ -26,10 +27,51 @@ erpnext.doctypes_with_dimensions.forEach((doctype) => {
|
||||
"is_group": 0
|
||||
});
|
||||
}
|
||||
if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) {
|
||||
|
||||
if (Object.keys(erpnext.default_dimensions).length > 0) {
|
||||
if (frappe.meta.has_field(doctype, dimension['fieldname'])) {
|
||||
if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) {
|
||||
frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]);
|
||||
}
|
||||
}
|
||||
|
||||
if (frm.doc.items && frm.doc.items.length) {
|
||||
frm.doc.items[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']];
|
||||
}
|
||||
|
||||
if (frm.doc.accounts && frm.doc.accounts.length) {
|
||||
frm.doc.accounts[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
if(frm.doc.company && (Object.keys(erpnext.default_dimensions).length > 0)) {
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
if (frappe.meta.has_field(doctype, dimension['fieldname'])) {
|
||||
frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
erpnext.child_docs.forEach((doctype) => {
|
||||
frappe.ui.form.on(doctype, {
|
||||
items_add: function(frm, cdt, cdn) {
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
frm.script_manager.copy_from_first_row("items", row, [dimension['fieldname']]);
|
||||
});
|
||||
},
|
||||
|
||||
accounts_add: function(frm, cdt, cdn) {
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
frm.script_manager.copy_from_first_row("accounts", row, [dimension['fieldname']]);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -156,33 +156,21 @@ class Gstr1Report(object):
|
||||
|
||||
|
||||
if self.filters.get("type_of_business") == "B2B":
|
||||
customers = frappe.get_all("Customer",
|
||||
filters={
|
||||
"gst_category": ["in", ["Registered Regular", "Deemed Export", "SEZ"]]
|
||||
})
|
||||
|
||||
if customers:
|
||||
conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1
|
||||
and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers]))
|
||||
conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1"
|
||||
|
||||
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
|
||||
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
|
||||
if not b2c_limit:
|
||||
frappe.throw(_("Please set B2C Limit in GST Settings."))
|
||||
|
||||
customers = frappe.get_all("Customer",
|
||||
filters={
|
||||
"gst_category": ["in", ["Unregistered"]]
|
||||
})
|
||||
|
||||
if self.filters.get("type_of_business") == "B2C Large" and customers:
|
||||
conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
|
||||
and grand_total > {0} and is_return != 1 and customer in ({1})""".\
|
||||
and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.\
|
||||
format(flt(b2c_limit), ", ".join([frappe.db.escape(c.name) for c in customers]))
|
||||
elif self.filters.get("type_of_business") == "B2C Small" and customers:
|
||||
conditions += """ and (
|
||||
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
|
||||
or grand_total <= {0}) and is_return != 1 and customer in ({1})""".\
|
||||
or grand_total <= {0}) and is_return != 1 and gst_category ='Unregistered' """.\
|
||||
format(flt(b2c_limit), ", ".join([frappe.db.escape(c.name) for c in customers]))
|
||||
|
||||
elif self.filters.get("type_of_business") == "CDNR":
|
||||
|
@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Customer-wise Item Price"] = {
|
||||
"filters": [
|
||||
{
|
||||
"label": __("Customer"),
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"label": __("Item"),
|
||||
"fieldname": "item",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"get_query": () => {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: { 'is_sales_item': 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"creation": "2019-06-12 03:25:36.263179",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"letter_head": "Delta9",
|
||||
"modified": "2019-06-12 03:25:36.263179",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer-wise Item Price",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Customer",
|
||||
"report_name": "Customer-wise Item Price",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"role": "Stock Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Sales Manager"
|
||||
},
|
||||
{
|
||||
"role": "Sales Master Manager"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from erpnext import get_default_company
|
||||
from erpnext.accounts.party import get_party_details
|
||||
from erpnext.stock.get_item_details import get_price_list_rate_for
|
||||
from frappe import _
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
if not filters.get("customer"):
|
||||
frappe.throw(_("Please select a Customer"))
|
||||
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns(filters=None):
|
||||
return [
|
||||
{
|
||||
"label": _("Item Code"),
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Item Name"),
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"label": _("Selling Rate"),
|
||||
"fieldname": "selling_rate",
|
||||
"fieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"label": _("Available Stock"),
|
||||
"fieldname": "available_stock",
|
||||
"fieldtype": "Float",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Price List"),
|
||||
"fieldname": "price_list",
|
||||
"fieldtype": "Link",
|
||||
"options": "Price List",
|
||||
"width": 120
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def get_data(filters=None):
|
||||
data = []
|
||||
customer_details = get_customer_details(filters)
|
||||
|
||||
items = get_selling_items(filters)
|
||||
item_stock_map = frappe.get_all("Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code")
|
||||
item_stock_map = {item.item_code: item.available for item in item_stock_map}
|
||||
|
||||
for item in items:
|
||||
price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0
|
||||
available_stock = item_stock_map.get(item.item_code)
|
||||
|
||||
data.append({
|
||||
"item_code": item.item_code,
|
||||
"item_name": item.item_name,
|
||||
"selling_rate": price_list_rate,
|
||||
"price_list": customer_details.get("price_list"),
|
||||
"available_stock": available_stock,
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_customer_details(filters):
|
||||
customer_details = get_party_details(party=filters.get("customer"), party_type="Customer")
|
||||
customer_details.update({
|
||||
"company": get_default_company(),
|
||||
"price_list": customer_details.get("selling_price_list")
|
||||
})
|
||||
|
||||
return customer_details
|
||||
|
||||
|
||||
def get_selling_items(filters):
|
||||
if filters.get("item"):
|
||||
item_filters = {"item_code": filters.get("item"), "is_sales_item": 1, "disabled": 0}
|
||||
else:
|
||||
item_filters = {"is_sales_item": 1, "disabled": 0}
|
||||
|
||||
items = frappe.get_all("Item", filters=item_filters, fields=["item_code", "item_name"], order_by="item_name")
|
||||
|
||||
return items
|
@ -701,6 +701,7 @@ def create_delivery_note(**args):
|
||||
"qty": args.qty or 1,
|
||||
"rate": args.rate or 100,
|
||||
"conversion_factor": 1.0,
|
||||
"allow_zero_valuation_rate": args.allow_zero_valuation_rate or 1,
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no,
|
||||
|
@ -39,11 +39,12 @@ cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
|
||||
query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
|
||||
filters: {
|
||||
"from": doctype,
|
||||
"parent": doc.reference_name
|
||||
"parent": doc.reference_name,
|
||||
"inspection_type": doc.inspection_type
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Serial No based on item_code
|
||||
cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) {
|
||||
|
@ -63,10 +63,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
mcond = get_match_cond(filters["from"])
|
||||
cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')"
|
||||
|
||||
if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']:
|
||||
if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']\
|
||||
and filters.get("inspection_type") != "In Process":
|
||||
cond = """and item_code in (select name from `tabItem` where
|
||||
inspection_required_before_purchase = 1)"""
|
||||
elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']:
|
||||
elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']\
|
||||
and filters.get("inspection_type") != "In Process":
|
||||
cond = """and item_code in (select name from `tabItem` where
|
||||
inspection_required_before_delivery = 1)"""
|
||||
elif filters.get('from') == 'Stock Entry Detail':
|
||||
|
@ -464,16 +464,22 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
|
||||
|
||||
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and warehouse = %s
|
||||
and valuation_rate >= 0
|
||||
order by posting_date desc, posting_time desc, creation desc limit 1""", (item_code, warehouse))
|
||||
where
|
||||
item_code = %s
|
||||
AND warehouse = %s
|
||||
AND valuation_rate >= 0
|
||||
AND NOT (voucher_no = %s AND voucher_type = %s)
|
||||
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
|
||||
|
||||
if not last_valuation_rate:
|
||||
# Get valuation rate from last sle for the item against any warehouse
|
||||
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and valuation_rate > 0
|
||||
order by posting_date desc, posting_time desc, creation desc limit 1""", item_code)
|
||||
where
|
||||
item_code = %s
|
||||
AND valuation_rate > 0
|
||||
AND NOT(voucher_no = %s AND voucher_type = %s)
|
||||
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type))
|
||||
|
||||
if last_valuation_rate:
|
||||
return flt(last_valuation_rate[0][0]) # as there is previous records, it might come with zero rate
|
||||
|
@ -32,7 +32,7 @@ frappe.ready(function() {
|
||||
if(r.message.product_info.in_stock===0) {
|
||||
$(".item-stock").html("<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>");
|
||||
}
|
||||
else if(r.message.product_info.in_stock===1) {
|
||||
else if(r.message.product_info.in_stock===1 && r.message.cart_settings.show_stock_availability) {
|
||||
var qty_display = "{{ _("In stock") }}";
|
||||
if (r.message.product_info.show_stock_qty) {
|
||||
qty_display += " ("+r.message.product_info.stock_qty+")";
|
||||
|
@ -7,7 +7,7 @@ class TestSearch(unittest.TestCase):
|
||||
#Search for the word "cond", part of the word "conduire" (Lead) in french.
|
||||
def test_contact_search_in_foreign_language(self):
|
||||
frappe.local.lang = 'fr'
|
||||
output = filter_dynamic_link_doctypes("DocType", "cond", "name", 0, 20, {'fieldtype': 'HTML', 'fieldname': 'contact_html'})
|
||||
output = filter_dynamic_link_doctypes("DocType", "prospect", "name", 0, 20, {'fieldtype': 'HTML', 'fieldname': 'contact_html'})
|
||||
|
||||
result = [['found' for x in y if x=="Lead"] for y in output]
|
||||
self.assertTrue(['found'] in result)
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,2 @@
|
||||
DocType: Account,Accounts,དངུལ་རྩིས།
|
||||
DocType: Pricing Rule,Buying,ཉོ་བ།
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@
|
||||
apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Åbning'
|
||||
DocType: Email Campaign,Lead,Bly
|
||||
apps/erpnext/erpnext/config/selling.py,Default settings for selling transactions.,Standardindstillinger for at sælge transaktioner.
|
||||
DocType: Timesheet,% Amount Billed,% Beløb Billed
|
||||
DocType: Purchase Order,% Billed,% Billed
|
||||
,Lead Id,Bly Id
|
||||
apps/erpnext/erpnext/accounts/doctype/payment_order/payment_order.py,{0} {1} created,{0} {1} creado
|
||||
apps/erpnext/erpnext/accounts/report/accounts_receivable/accounts_receivable.html,'Total','Total'
|
||||
DocType: Selling Settings,Selling Settings,Salg af indstillinger
|
||||
apps/erpnext/erpnext/accounts/report/gross_profit/gross_profit.py,Selling Amount,Selling Beløb
|
||||
DocType: Item Default,Default Selling Cost Center,Standard Selling Cost center
|
||||
apps/erpnext/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py,90-Above,90-Above
|
||||
DocType: Pricing Rule,Selling,Selling
|
||||
DocType: Sales Order,% Delivered,% Leveres
|
||||
DocType: Lead,Lead Owner,Bly Owner
|
||||
apps/erpnext/erpnext/controllers/stock_controller.py,{0} {1}: Cost Center is mandatory for Item {2},{0} {1}: Udgiftområde er obligatorisk for varen {2}
|
||||
apps/erpnext/erpnext/config/accounting.py,Tax template for selling transactions.,Skat skabelon til at sælge transaktioner.
|
||||
apps/erpnext/erpnext/controllers/accounts_controller.py, or ,o
|
||||
DocType: Sales Order,% of materials billed against this Sales Order,% Af materialer faktureret mod denne Sales Order
|
||||
DocType: SMS Center,All Lead (Open),Alle Bly (Open)
|
||||
apps/erpnext/erpnext/templates/includes/footer/footer_extension.html,Get Updates,Hent opdateringer
|
||||
apps/erpnext/erpnext/controllers/sales_and_purchase_return.py,'Update Stock' can not be checked because items are not delivered via {0},"'Opdater lager' kan ikke markeres, varerne ikke leveres via {0}"
|
||||
apps/erpnext/erpnext/patches/v4_0/create_price_list_if_missing.py,Standard Selling,Standard Selling
|
||||
,Lead Details,Bly Detaljer
|
||||
DocType: Selling Settings,Settings for Selling Module,Indstillinger for Selling modul
|
||||
,Lead Name,Bly navn
|
||||
DocType: Rename Tool,"Attach .csv file with two columns, one for the old name and one for the new name","Vedhæfte .csv fil med to kolonner, en for det gamle navn og et til det nye navn"
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
DocType: Patient,Married,既婚
|
|
@ -0,0 +1,2 @@
|
||||
apps/erpnext/erpnext/controllers/accounts_controller.py, or ,uor
|
||||
apps/erpnext/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py,90-Above,90-Above
|
|
@ -0,0 +1,46 @@
|
||||
apps/erpnext/erpnext/accounts/report/accounts_receivable/accounts_receivable.html,Cheques Required,Checks Required
|
||||
apps/erpnext/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py,Row #{0}: Clearance date {1} cannot be before Cheque Date {2},Row #{0}: Clearance date {1} cannot be before Check Date {2}
|
||||
apps/erpnext/erpnext/utilities/user_progress.py,People who teach at your organisation,People who teach at your organization
|
||||
apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,"Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Leave cannot be applied/canceled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}"
|
||||
apps/erpnext/erpnext/support/doctype/warranty_claim/warranty_claim.py,Cancel Material Visit {0} before cancelling this Warranty Claim,Cancel Material Visit {0} before canceling this Warranty Claim
|
||||
apps/erpnext/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py,"Appointment cancelled, Please review and cancel the invoice {0}","Appointment canceled, Please review and cancel the invoice {0}"
|
||||
apps/erpnext/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py,Outstanding Cheques and Deposits to clear,Outstanding Checks and Deposits to clear
|
||||
apps/erpnext/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py,Appointment cancelled,Appointment canceled
|
||||
DocType: Payment Entry,Cheque/Reference Date,Check/Reference Date
|
||||
DocType: Cheque Print Template,Scanned Cheque,Scanned Check
|
||||
DocType: Cheque Print Template,Cheque Size,Check Size
|
||||
apps/erpnext/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py,Maintenance Status has to be Cancelled or Completed to Submit,Maintenance Status has to be Canceled or Completed to Submit
|
||||
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py,'Entries' cannot be empty,'Entries' can not be empty
|
||||
apps/erpnext/erpnext/setup/doctype/company/company.py,"Cannot change company's default currency, because there are existing transactions. Transactions must be cancelled to change the default currency.","Cannot change company's default currency, because there are existing transactions. Transactions must be canceled to change the default currency."
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Maintenance Visit {0} must be cancelled before cancelling this Sales Order,Maintenance Visit {0} must be canceled before cancelling this Sales Order
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Sales Invoice {0} must be cancelled before cancelling this Sales Order,Sales Invoice {0} must be canceled before cancelling this Sales Order
|
||||
DocType: Bank Reconciliation Detail,Cheque Date,Check Date
|
||||
apps/erpnext/erpnext/manufacturing/doctype/work_order/work_order.py,"Stopped Work Order cannot be cancelled, Unstop it first to cancel","Stopped Work Order cannot be canceled, Unstop it first to cancel"
|
||||
apps/erpnext/erpnext/buying/doctype/purchase_order/purchase_order.py,Material Request {0} is cancelled or stopped,Material Request {0} is canceled or stopped
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Closed order cannot be cancelled. Unclose to cancel.,Closed order cannot be canceled. Unclose to cancel.
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Maintenance Schedule {0} must be cancelled before cancelling this Sales Order,Maintenance Schedule {0} must be canceled before cancelling this Sales Order
|
||||
DocType: Accounts Settings,Unlink Payment on Cancellation of Invoice,Unlink Payment on Cancelation of Invoice
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Delivery Notes {0} must be cancelled before cancelling this Sales Order,Delivery Notes {0} must be canceled before cancelling this Sales Order
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Work Order {0} must be cancelled before cancelling this Sales Order,Work Order {0} must be canceled before cancelling this Sales Order
|
||||
apps/erpnext/erpnext/config/accounting.py,Setup cheque dimensions for printing,Setup check dimensions for printing
|
||||
apps/erpnext/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py,Cheques and Deposits incorrectly cleared,Checks and Deposits incorrectly cleared
|
||||
apps/erpnext/erpnext/stock/doctype/material_request/material_request.py,{0} {1} is cancelled so the action cannot be completed,{0} {1} is canceled so the action cannot be completed
|
||||
apps/erpnext/erpnext/stock/doctype/delivery_note/delivery_note.py,Packing Slip(s) cancelled,Packing Slip(s) canceled
|
||||
DocType: Payment Entry,Cheque/Reference No,Check/Reference No
|
||||
apps/erpnext/erpnext/assets/doctype/asset/asset.py,"Asset cannot be cancelled, as it is already {0}","Asset cannot be canceled, as it is already {0}"
|
||||
DocType: Bank Reconciliation,Select account head of the bank where cheque was deposited.,Select account head of the bank where check was deposited.
|
||||
DocType: Cheque Print Template,Cheque Print Template,Check Print Template
|
||||
apps/erpnext/erpnext/controllers/selling_controller.py,{0} {1} is cancelled or closed,{0} {1} is canceled or closed
|
||||
apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Quotation {0} is cancelled,Quotation {0} is canceled
|
||||
apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.py,Timesheet {0} is already completed or cancelled,Timesheet {0} is already completed or canceled
|
||||
apps/erpnext/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py,Payment Cancelled. Please check your GoCardless Account for more details,Payment Canceled. Please check your GoCardless Account for more details
|
||||
apps/erpnext/erpnext/stock/doctype/item/item.py,Item {0} is cancelled,Item {0} is canceled
|
||||
DocType: Serial No,Is Cancelled,Is Canceled
|
||||
apps/erpnext/erpnext/stock/doctype/material_request/material_request.py,{0} {1} is cancelled or stopped,{0} {1} is canceled or stopped
|
||||
apps/erpnext/erpnext/setup/setup_wizard/operations/install_fixtures.py,Colour,Color
|
||||
DocType: Bank Reconciliation Detail,Cheque Number,Check Number
|
||||
apps/erpnext/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py,Cancel Material Visits {0} before cancelling this Maintenance Visit,Cancel Material Visits {0} before canceling this Maintenance Visit
|
||||
DocType: Employee,Cheque,Check
|
||||
DocType: Cheque Print Template,Cheque Height,Check Height
|
||||
DocType: Cheque Print Template,Cheque Width,Check Width
|
||||
apps/erpnext/erpnext/setup/setup_wizard/operations/install_fixtures.py,Wire Transfer,Wire Transfer
|
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user